Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 94 additions & 9 deletions songpal/containers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Data containers for Songpal."""
import logging
from datetime import timedelta
from typing import List, Optional
from typing import List, Optional, Union

import attr

from songpal import SongpalException

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -271,7 +273,6 @@ class Volume:

make = classmethod(make)

services = attr.ib(repr=False)
maxVolume = attr.ib()
minVolume = attr.ib()
mute = attr.ib()
Expand All @@ -282,7 +283,7 @@ class Volume:
@property
def is_muted(self):
"""Return True if volume is muted."""
return self.mute == "on"
return self.mute == "on" or self.mute is True

def __str__(self):
if self.output and self.output.rfind("=") > 0:
Expand All @@ -299,6 +300,22 @@ def __str__(self):

async def set_mute(self, activate: bool):
"""Set mute on/off."""
raise NotImplementedError

async def toggle_mute(self):
"""Toggle mute."""
raise NotImplementedError

async def set_volume(self, volume: Union[str, int]):
"""Set volume level."""
raise NotImplementedError


@attr.s
class VolumeControlSongpal(Volume):
services = attr.ib(repr=False)

async def set_mute(self, activate: bool):
enabled = "off"
if activate:
enabled = "on"
Expand All @@ -308,18 +325,51 @@ async def set_mute(self, activate: bool):
)

async def toggle_mute(self):
"""Toggle mute."""
return await self.services["audio"]["setAudioMute"](
mute="toggle", output=self.output
)

async def set_volume(self, volume: int):
"""Set volume level."""
async def set_volume(self, volume: Union[str, int]):
return await self.services["audio"]["setAudioVolume"](
volume=str(volume), output=self.output
)


@attr.s
class VolumeControlUpnp(Volume):

renderingControl = attr.ib(default=None)

async def set_mute(self, activate: bool):
"""Set mute on/off."""

return await self.renderingControl.action("SetMute").async_call(
InstanceID=0, Channel="Master", DesiredMute=activate
)

async def toggle_mute(self):
mute_result = await self.renderingControl.action("GetMute").async_call(
InstanceID=0, Channel="Master"
)
return self.set_mute(not mute_result["CurrentMute"])

async def set_volume(self, volume: Union[str, int]):
if isinstance(volume, str):
if "+" in volume or "-" in volume:
raise SongpalException(
"Setting relative volume not supported with UPnP"
)
desired_volume = int(volume)
elif isinstance(volume, int):
desired_volume = volume
else:
raise SongpalException("Invalid volume %s" % volume)

return await self.renderingControl.action("SetVolume").async_call(
InstanceID=0, Channel="Master", DesiredVolume=desired_volume
)


@attr.s
class Power:
"""Information about power status.
Expand Down Expand Up @@ -377,12 +427,9 @@ class Input:

make = classmethod(make)

meta = attr.ib()
connection = attr.ib()
title = attr.ib(converter=convert_title)
uri = attr.ib()

services = attr.ib(repr=False)
active = attr.ib(converter=convert_is_active)
label = attr.ib()
iconUrl = attr.ib()
Expand All @@ -396,12 +443,50 @@ def __str__(self):

async def activate(self, output: Zone = None):
"""Activate this input."""
raise NotImplementedError


@attr.s
class InputControlSongpal(Input):
meta = attr.ib(default=None)
connection = attr.ib(default=None)
services = attr.ib(default=None, repr=False)

def __str__(self):
s = "%s (uri: %s)" % (self.title, self.uri)
if self.active:
s += " (active)"
return s

async def activate(self, output: Zone = None):
output_uri = output.uri if output else ""
return await self.services["avContent"]["setPlayContent"](
uri=self.uri, output=output_uri
)


@attr.s
class InputControlUpnp(Input):

avTransport = attr.ib(default=None)
uriMetadata = attr.ib(default=None)

async def activate(self, output: Zone = None):
result = await self.avTransport.action("SetAVTransportURI").async_call(
InstanceID=0, CurrentURI=self.uri, CurrentURIMetaData=self.uriMetadata
)

try:
# Attempt to play as the songpal app is doing after changing input,
# sometimes needed so that input emits sound
await self.avTransport.action("Play").async_call(InstanceID=0, Speed="1")
except Exception:
# Play action can cause 500 error in certain cases
pass

return result


@attr.s
class Storage:
"""Storage information."""
Expand Down
Loading