""" This module provides an interface to control device groups.. In contrast to most of the features in the library, the groups are controlled using UPnP. This class implements urn:schemas-sony-com:service:Group:1 UPnP service. """ import logging import attr from async_upnp_client.aiohttp import AiohttpRequester from async_upnp_client.client_factory import UpnpFactory from .containers import make _LOGGER = logging.getLogger(__name__) @attr.s class GroupState: """Container for group state information.""" make = classmethod(make) """{'Discoverable': 'NO', 'GroupMode': 'IDLE', 'GroupName': '', 'GroupSong': 'PUBLIC', 'GroupState': 'IDLE', 'MasterSessionID': 0, 'MasterUUID': '', 'NumberOfSlaves': 0, 'PlayingState': 'STOPPED', 'PowerState': 'ON', 'RSSIValue': -46, 'SessionID': 0, 'SlaveList': '', 'SlaveNetworkState': '', 'WiredLinkSpeed': 0, 'WiredState': 'DOWN', 'WirelessLinkSpeed': 65, 'WirelessState': 'UP', 'WirelessType': '802.11bgn'} """ Discoverable = attr.ib() GroupMode = attr.ib() GroupName = attr.ib() GroupSong = attr.ib() GroupState = attr.ib() MasterSessionID = attr.ib() MasterUUID = attr.ib() NumberOfSlaves = attr.ib() PlayingState = attr.ib() PowerState = attr.ib() RSSIValue = attr.ib() SessionID = attr.ib() SlaveList = attr.ib() SlaveNetworkState = attr.ib() WiredLinkSpeed = attr.ib() WiredState = attr.ib() WirelessLinkSpeed = attr.ib() WirelessState = attr.ib() WirelessType = attr.ib() # For GetStateM GroupMemoryCount = attr.ib(default=None) GroupMemoryUpdateID = attr.ib(default=None) def __str__(self): s = "Power: %s" % self.PowerState s += "\nMode: %s" % self.GroupMode if self.GroupMode == "GROUP": s += "\nSession ID: %s" % self.SessionID s += "\nGroup: %s" % self.GroupName s += "\nState: %s" % self.GroupState s += "\nSlaves: %s" % self.NumberOfSlaves s += "\n %s" % self.SlaveList if self.WiredState != "DOWN": s += "\nConnection: Wired" if self.WirelessState != "DOWN": s += "\nConnection: %s" % self.WirelessType return s class GroupControl: """Class for controlling speaker groups. This provides an interface to control device groups using UPnP interface 'urn:schemas-sony-com:service:Group:1'. """ def __init__(self, url): self.url = url async def connect(self): """Connect and initialize the controls. Returns False if the UPnP service is not found. """ requester = AiohttpRequester() factory = UpnpFactory(requester) device = await factory.async_create_device(self.url) self.service = device.service("urn:schemas-sony-com:service:Group:1") if not self.service: _LOGGER.error("Unable to find group service!") return False for act in self.service.actions.values(): _LOGGER.debug( "Action: %s (%s)", act, [arg.name for arg in act.in_arguments()] ) return True """ Available actions ([]) ([]) ([]) (['GroupName']) (['GroupVolume']) ([]) (['MemoryID']) (['MemoryID', 'GroupMode', 'GroupName', 'SlaveList', 'CodecType', 'CodecBitrate']) (['GroupMode', 'GroupName', 'SlaveList', 'CodecType', 'CodecBitrate']) (['MasterSessionID', 'SlaveList']) (['MasterSessionID', 'SlaveList']) (['MasterSessionID', 'SlaveList']) (['MasterSessionID', 'SlaveList']) (['MasterSessionID']) (['GroupMute']) (['CodecType', 'CodecBitrate']) ([]) (['GroupMode', 'GroupName', 'MasterUUID', 'MasterSessionID']) (['SlaveSessionID']) (['MasterSessionID']) (['MasterSessionID']) (['GroupMode', 'SlaveList', 'DelegateURI', 'DelegateURIMetaData']) """ async def call(self, action, **kwargs): """Make an action call with given kwargs.""" act = self.service.action(action) _LOGGER.info("Calling %s with %s", action, kwargs) res = await act.async_call(**kwargs) _LOGGER.info(" Result: %s" % res) return res async def info(self): """Return device info.""" """ {'MasterCapability': 9, 'TransportPort': 3975} """ act = self.service.action("X_GetDeviceInfo") res = await act.async_call() return res async def state(self) -> GroupState: """Return the current group state.""" act = self.service.action("X_GetState") res = await act.async_call() return GroupState.make(**res) async def statem(self) -> GroupState: """Return the current group state (memory?).""" act = self.service.action("X_GetStateM") res = await act.async_call() return GroupState.make(**res) async def get_group_memory(self): """Return group memory.""" # Returns an XML with groupMemoryList act = self.service.action("X_GetAllGroupMemory") res = await act.async_call() return res async def update_group_memory( self, memory_id, mode, name, slaves, codectype=0x0040, bitrate=0x0003 ): """Update existing memory. Unknown if this can be used to create new ones, too. """ act = self.service.action("X_UpdateGroupMemory") res = await act.async_call( MemoryID=memory_id, GroupMode=mode, GroupName=name, SlaveList=slaves, CodecType=codectype, CodecBitrate=bitrate, ) return res async def delete_group_memory(self, memory_id): """Delete group memory.""" act = self.service.action("X_DeleteGroupMemory") return await act.async_call(MemoryID=memory_id) async def get_codec(self): """Get codec settings.""" act = self.service.action("X_GetCodec") res = await act.async_call() return res async def set_codec(self, codectype=0x0040, bitrate=0x0003): """Set codec settings.""" act = self.service.action("X_SetCodec") res = await act.async_call(CodecType=codectype, CodecBitrate=bitrate) return res async def abort(self): """Abort current group session.""" state = await self.state() res = await self.call("X_Abort", MasterSessionID=state.SessionID) return res async def stop(self): """Stop playback.""" state = await self.state() res = await self.call("X_Stop", MasterSessionID=state.SessionID) return res async def play(self): """Start playback.""" state = await self.state() res = await self.call("X_Play", MasterSessionID=state.SessionID) return res async def create(self, name, slaves): """Create a group.""" # NOTE: codectype and codecbitrate were simply chosen from an example.. res = await self.call( "X_Start", GroupMode="GROUP", GroupName=name, SlaveList=",".join(slaves), CodecType=0x0040, CodecBitrate=0x0003, ) return res async def add(self, slaves): """Add slaves to the current group.""" state = await self.state() res = await self.call( "X_Entry", MasterSessionID=state.SessionID, SlaveList=slaves ) return res async def add_m(self, slaves): """Unknown usage.""" state = await self.state() return await self.call( "X_EntryM", MasterSessionID=state.SessionID, SlaveList=slaves ) async def remove(self, slaves): """Remove slaves from the current group.""" state = await self.state() return await self.call( "X_Leave", MasterSessionID=state.SessionID, SlaveList=slaves ) async def remove_m(self, slaves): """Unknown usage.""" state = await self.state() return await self.call( "X_LeaveM", MasterSessionID=state.SessionID, SlaveList=slaves ) async def set_mute(self, activate): """Set group mute.""" res = await self.call("X_SetGroupMute", GroupMute=activate) return res async def set_group_volume(self, volume): """Set group volume.""" res = await self.call("X_ChangeGroupVolume", GroupVolume=volume) return res async def set_group_name(self, name): """Set group name.""" res = await self.call("X_SetGroupName", GroupName=name) return res