Bugfix sonos / refactor of sonos function for TTS (#5571)
* Bugfix sonos / refactor of sonos function for TTS * fix unittest * update service yaml * restore group of a coordinator * use group function to evaluate * fix state flooting * fix comments
This commit is contained in:
parent
41218e5a37
commit
4831f57834
4 changed files with 255 additions and 201 deletions
|
@ -154,10 +154,14 @@ clear_playlist:
|
|||
description: Name(s) of entites to change source on
|
||||
example: 'media_player.living_room_chromecast'
|
||||
|
||||
sonos_group_players:
|
||||
description: Send Sonos media player the command for grouping all players into one (party mode).
|
||||
sonos_join:
|
||||
description: Group player together.
|
||||
|
||||
fields:
|
||||
master:
|
||||
description: Entity ID of the player that should become the coordinator of the group.
|
||||
example: 'media_player.living_room_sonos'
|
||||
|
||||
entity_id:
|
||||
description: Name(s) of entites that will coordinate the grouping. Platform dependent.
|
||||
example: 'media_player.living_room_sonos'
|
||||
|
@ -178,6 +182,10 @@ sonos_snapshot:
|
|||
description: Name(s) of entites that will be snapshot. Platform dependent.
|
||||
example: 'media_player.living_room_sonos'
|
||||
|
||||
with_group:
|
||||
description: True (default) or False. Snapshot with all group attributes.
|
||||
example: 'true'
|
||||
|
||||
sonos_restore:
|
||||
description: Restore a snapshot of the media player.
|
||||
|
||||
|
@ -186,6 +194,10 @@ sonos_restore:
|
|||
description: Name(s) of entites that will be restored. Platform dependent.
|
||||
example: 'media_player.living_room_sonos'
|
||||
|
||||
with_group:
|
||||
description: True (default) or False. Restore with all group attributes.
|
||||
example: 'true'
|
||||
|
||||
sonos_set_sleep_timer:
|
||||
description: Set a Sonos timer
|
||||
|
||||
|
|
|
@ -42,13 +42,15 @@ SUPPORT_SONOS = SUPPORT_STOP | SUPPORT_PAUSE | SUPPORT_VOLUME_SET |\
|
|||
SUPPORT_PLAY_MEDIA | SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST |\
|
||||
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
|
||||
|
||||
SERVICE_GROUP_PLAYERS = 'sonos_group_players'
|
||||
SERVICE_JOIN = 'sonos_join'
|
||||
SERVICE_UNJOIN = 'sonos_unjoin'
|
||||
SERVICE_SNAPSHOT = 'sonos_snapshot'
|
||||
SERVICE_RESTORE = 'sonos_restore'
|
||||
SERVICE_SET_TIMER = 'sonos_set_sleep_timer'
|
||||
SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer'
|
||||
|
||||
DATA_SONOS = 'sonos'
|
||||
|
||||
SUPPORT_SOURCE_LINEIN = 'Line-in'
|
||||
SUPPORT_SOURCE_TV = 'TV'
|
||||
|
||||
|
@ -57,6 +59,8 @@ CONF_INTERFACE_ADDR = 'interface_addr'
|
|||
|
||||
# Service call validation schemas
|
||||
ATTR_SLEEP_TIME = 'sleep_time'
|
||||
ATTR_MASTER = 'master'
|
||||
ATTR_WITH_GROUP = 'with_group'
|
||||
|
||||
ATTR_IS_COORDINATOR = 'is_coordinator'
|
||||
|
||||
|
@ -70,19 +74,26 @@ SONOS_SCHEMA = vol.Schema({
|
|||
ATTR_ENTITY_ID: cv.entity_ids,
|
||||
})
|
||||
|
||||
SONOS_SET_TIMER_SCHEMA = SONOS_SCHEMA.extend({
|
||||
vol.Required(ATTR_SLEEP_TIME): vol.All(vol.Coerce(int),
|
||||
vol.Range(min=0, max=86399))
|
||||
SONOS_JOIN_SCHEMA = SONOS_SCHEMA.extend({
|
||||
vol.Required(ATTR_MASTER): cv.entity_id,
|
||||
})
|
||||
|
||||
# List of devices that have been registered
|
||||
DEVICES = []
|
||||
SONOS_STATES_SCHEMA = SONOS_SCHEMA.extend({
|
||||
vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean,
|
||||
})
|
||||
|
||||
SONOS_SET_TIMER_SCHEMA = SONOS_SCHEMA.extend({
|
||||
vol.Required(ATTR_SLEEP_TIME):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=0, max=86399))
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Sonos platform."""
|
||||
import soco
|
||||
global DEVICES
|
||||
|
||||
if DATA_SONOS not in hass.data:
|
||||
hass.data[DATA_SONOS] = []
|
||||
|
||||
advertise_addr = config.get(CONF_ADVERTISE_ADDR, None)
|
||||
if advertise_addr:
|
||||
|
@ -92,154 +103,92 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
player = soco.SoCo(discovery_info)
|
||||
|
||||
# if device allready exists by config
|
||||
if player.uid in [x.unique_id for x in DEVICES]:
|
||||
return True
|
||||
if player.uid in [x.unique_id for x in hass.data[DATA_SONOS]]:
|
||||
return
|
||||
|
||||
if player.is_visible:
|
||||
device = SonosDevice(hass, player)
|
||||
add_devices([device], True)
|
||||
if not DEVICES:
|
||||
register_services(hass)
|
||||
DEVICES.append(device)
|
||||
return True
|
||||
return False
|
||||
hass.data[DATA_SONOS].append(device)
|
||||
if len(hass.data[DATA_SONOS]) > 1:
|
||||
return
|
||||
else:
|
||||
players = None
|
||||
hosts = config.get(CONF_HOSTS, None)
|
||||
if hosts:
|
||||
# Support retro compatibility with comma separated list of hosts
|
||||
# from config
|
||||
hosts = hosts[0] if len(hosts) == 1 else hosts
|
||||
hosts = hosts.split(',') if isinstance(hosts, str) else hosts
|
||||
players = []
|
||||
for host in hosts:
|
||||
players.append(soco.SoCo(socket.gethostbyname(host)))
|
||||
|
||||
players = None
|
||||
hosts = config.get(CONF_HOSTS, None)
|
||||
if hosts:
|
||||
# Support retro compatibility with comma separated list of hosts
|
||||
# from config
|
||||
hosts = hosts[0] if len(hosts) == 1 else hosts
|
||||
hosts = hosts.split(',') if isinstance(hosts, str) else hosts
|
||||
players = []
|
||||
for host in hosts:
|
||||
players.append(soco.SoCo(socket.gethostbyname(host)))
|
||||
if not players:
|
||||
players = soco.discover(
|
||||
interface_addr=config.get(CONF_INTERFACE_ADDR))
|
||||
|
||||
if not players:
|
||||
players = soco.discover(interface_addr=config.get(CONF_INTERFACE_ADDR))
|
||||
if not players:
|
||||
_LOGGER.warning('No Sonos speakers found.')
|
||||
return
|
||||
|
||||
if not players:
|
||||
_LOGGER.warning('No Sonos speakers found.')
|
||||
return False
|
||||
hass.data[DATA_SONOS] = [SonosDevice(hass, p) for p in players]
|
||||
add_devices(hass.data[DATA_SONOS], True)
|
||||
_LOGGER.info('Added %s Sonos speakers', len(players))
|
||||
|
||||
DEVICES = [SonosDevice(hass, p) for p in players]
|
||||
add_devices(DEVICES, True)
|
||||
register_services(hass)
|
||||
_LOGGER.info('Added %s Sonos speakers', len(players))
|
||||
return True
|
||||
|
||||
|
||||
def register_services(hass):
|
||||
"""Register all services for sonos devices."""
|
||||
descriptions = load_yaml_config_file(
|
||||
path.join(path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_GROUP_PLAYERS,
|
||||
_group_players_service,
|
||||
descriptions.get(SERVICE_GROUP_PLAYERS),
|
||||
schema=SONOS_SCHEMA)
|
||||
def service_handle(service):
|
||||
"""Internal func for applying a service."""
|
||||
entity_ids = service.data.get('entity_id')
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_UNJOIN,
|
||||
_unjoin_service,
|
||||
descriptions.get(SERVICE_UNJOIN),
|
||||
schema=SONOS_SCHEMA)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SNAPSHOT,
|
||||
_snapshot_service,
|
||||
descriptions.get(SERVICE_SNAPSHOT),
|
||||
schema=SONOS_SCHEMA)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_RESTORE,
|
||||
_restore_service,
|
||||
descriptions.get(SERVICE_RESTORE),
|
||||
schema=SONOS_SCHEMA)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_SET_TIMER,
|
||||
_set_sleep_timer_service,
|
||||
descriptions.get(SERVICE_SET_TIMER),
|
||||
schema=SONOS_SET_TIMER_SCHEMA)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_CLEAR_TIMER,
|
||||
_clear_sleep_timer_service,
|
||||
descriptions.get(SERVICE_CLEAR_TIMER),
|
||||
schema=SONOS_SCHEMA)
|
||||
|
||||
|
||||
def _apply_service(service, service_func, *service_func_args):
|
||||
"""Internal func for applying a service."""
|
||||
entity_ids = service.data.get('entity_id')
|
||||
|
||||
if entity_ids:
|
||||
_devices = [device for device in DEVICES
|
||||
if device.entity_id in entity_ids]
|
||||
else:
|
||||
_devices = DEVICES
|
||||
|
||||
for device in _devices:
|
||||
service_func(device, *service_func_args)
|
||||
device.update_ha_state(True)
|
||||
|
||||
|
||||
def _group_players_service(service):
|
||||
"""Group media players, use player as coordinator."""
|
||||
_apply_service(service, SonosDevice.group_players)
|
||||
|
||||
|
||||
def _unjoin_service(service):
|
||||
"""Unjoin the player from a group."""
|
||||
_apply_service(service, SonosDevice.unjoin)
|
||||
|
||||
|
||||
def _snapshot_service(service):
|
||||
"""Take a snapshot."""
|
||||
_apply_service(service, SonosDevice.snapshot)
|
||||
|
||||
|
||||
def _restore_service(service):
|
||||
"""Restore a snapshot."""
|
||||
_apply_service(service, SonosDevice.restore)
|
||||
|
||||
|
||||
def _set_sleep_timer_service(service):
|
||||
"""Set a timer."""
|
||||
_apply_service(service,
|
||||
SonosDevice.set_sleep_timer,
|
||||
service.data[ATTR_SLEEP_TIME])
|
||||
|
||||
|
||||
def _clear_sleep_timer_service(service):
|
||||
"""Set a timer."""
|
||||
_apply_service(service,
|
||||
SonosDevice.clear_sleep_timer)
|
||||
|
||||
|
||||
def only_if_coordinator(func):
|
||||
"""Decorator for coordinator.
|
||||
|
||||
If used as decorator, avoid calling the decorated method if player is not
|
||||
a coordinator. If not, a grouped speaker (not in coordinator role) will
|
||||
throw soco.exceptions.SoCoSlaveException.
|
||||
|
||||
Also, partially catch exceptions like:
|
||||
|
||||
soco.exceptions.SoCoUPnPException: UPnP Error 701 received:
|
||||
Transition not available from <player ip address>
|
||||
"""
|
||||
def wrapper(*args, **kwargs):
|
||||
"""Decorator wrapper."""
|
||||
if args[0].is_coordinator:
|
||||
from soco.exceptions import SoCoUPnPException
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
except SoCoUPnPException:
|
||||
_LOGGER.error('command "%s" for Sonos device "%s" '
|
||||
'not available in this mode',
|
||||
func.__name__, args[0].name)
|
||||
if entity_ids:
|
||||
devices = [device for device in hass.data[DATA_SONOS]
|
||||
if device.entity_id in entity_ids]
|
||||
else:
|
||||
_LOGGER.debug('Ignore command "%s" for Sonos device "%s" (%s)',
|
||||
func.__name__, args[0].name, 'not coordinator')
|
||||
devices = hass.data[DATA_SONOS]
|
||||
|
||||
return wrapper
|
||||
for device in devices:
|
||||
if service.service == SERVICE_JOIN:
|
||||
if device.entity_id != service.data[ATTR_MASTER]:
|
||||
device.join(service.data[ATTR_MASTER])
|
||||
elif service.service == SERVICE_UNJOIN:
|
||||
device.unjoin()
|
||||
elif service.service == SERVICE_SNAPSHOT:
|
||||
device.snapshot(service.data[ATTR_WITH_GROUP])
|
||||
elif service.service == SERVICE_RESTORE:
|
||||
device.restore(service.data[ATTR_WITH_GROUP])
|
||||
elif service.service == SERVICE_SET_TIMER:
|
||||
device.set_timer(service.data[ATTR_SLEEP_TIME])
|
||||
elif service.service == SERVICE_CLEAR_TIMER:
|
||||
device.clear_timer()
|
||||
|
||||
device.schedule_update_ha_state(True)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_JOIN, service_handle,
|
||||
descriptions.get(SERVICE_JOIN), schema=SONOS_JOIN_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_UNJOIN, service_handle,
|
||||
descriptions.get(SERVICE_UNJOIN), schema=SONOS_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SNAPSHOT, service_handle,
|
||||
descriptions.get(SERVICE_SNAPSHOT), schema=SONOS_STATES_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_RESTORE, service_handle,
|
||||
descriptions.get(SERVICE_RESTORE), schema=SONOS_STATES_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SET_TIMER, service_handle,
|
||||
descriptions.get(SERVICE_SET_TIMER), schema=SONOS_SET_TIMER_SCHEMA)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_CLEAR_TIMER, service_handle,
|
||||
descriptions.get(SERVICE_CLEAR_TIMER), schema=SONOS_SCHEMA)
|
||||
|
||||
|
||||
def _parse_timespan(timespan):
|
||||
|
@ -264,6 +213,14 @@ class _ProcessSonosEventQueue():
|
|||
self._sonos_device.process_sonos_event(item)
|
||||
|
||||
|
||||
def _get_entity_from_soco(hass, soco):
|
||||
"""Return SonosDevice from SoCo."""
|
||||
for device in hass.data[DATA_SONOS]:
|
||||
if soco == device.soco_device:
|
||||
return device
|
||||
raise ValueError("No entity for SoCo device!")
|
||||
|
||||
|
||||
class SonosDevice(MediaPlayerDevice):
|
||||
"""Representation of a Sonos device."""
|
||||
|
||||
|
@ -304,6 +261,7 @@ class SonosDevice(MediaPlayerDevice):
|
|||
self._favorite_sources = None
|
||||
self._source_name = None
|
||||
self.soco_snapshot = Snapshot(self._player)
|
||||
self._snapshot_group = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -338,6 +296,16 @@ class SonosDevice(MediaPlayerDevice):
|
|||
"""Return true if player is a coordinator."""
|
||||
return self._coordinator is None
|
||||
|
||||
@property
|
||||
def soco_device(self):
|
||||
"""Return soco device."""
|
||||
return self._player
|
||||
|
||||
@property
|
||||
def coordinator(self):
|
||||
"""Return coordinator of this player."""
|
||||
return self._coordinator
|
||||
|
||||
def _is_available(self):
|
||||
try:
|
||||
sock = socket.create_connection(
|
||||
|
@ -374,6 +342,19 @@ class SonosDevice(MediaPlayerDevice):
|
|||
|
||||
if is_available:
|
||||
|
||||
if self._player.group.coordinator != self._player:
|
||||
try:
|
||||
self._coordinator = _get_entity_from_soco(
|
||||
self.hass, self._player.group.coordinator)
|
||||
except ValueError:
|
||||
self._coordinator = None
|
||||
else:
|
||||
self._coordinator = None
|
||||
|
||||
if self._coordinator == self:
|
||||
_LOGGER.warning("Coordinator loop on: %s", self.unique_id)
|
||||
self._coordinator = None
|
||||
|
||||
track_info = None
|
||||
if self._last_avtransport_event:
|
||||
variables = self._last_avtransport_event.variables
|
||||
|
@ -404,16 +385,6 @@ class SonosDevice(MediaPlayerDevice):
|
|||
if not track_info:
|
||||
track_info = self._player.get_current_track_info()
|
||||
|
||||
if track_info['uri'].startswith('x-rincon:'):
|
||||
# this speaker is a slave, find the coordinator
|
||||
# the uri of the track is 'x-rincon:{coordinator-id}'
|
||||
coordinator_id = track_info['uri'][9:]
|
||||
coordinators = [device for device in DEVICES
|
||||
if device.unique_id == coordinator_id]
|
||||
self._coordinator = coordinators[0] if coordinators else None
|
||||
else:
|
||||
self._coordinator = None
|
||||
|
||||
if not self._coordinator:
|
||||
|
||||
is_playing_tv = self._player.is_playing_tv
|
||||
|
@ -550,6 +521,10 @@ class SonosDevice(MediaPlayerDevice):
|
|||
update_media_position |= rel_time is not None and \
|
||||
self._media_position is None
|
||||
|
||||
# used only if a media is playing
|
||||
if self.state != STATE_PLAYING:
|
||||
update_media_position = None
|
||||
|
||||
# position changed?
|
||||
if rel_time is not None and \
|
||||
self._media_position is not None:
|
||||
|
@ -616,10 +591,10 @@ class SonosDevice(MediaPlayerDevice):
|
|||
self._source_name = source_name
|
||||
|
||||
# update state of the whole group
|
||||
# pylint: disable=protected-access
|
||||
for device in [x for x in DEVICES if x._coordinator == self]:
|
||||
for device in [x for x in self.hass.data[DATA_SONOS]
|
||||
if x.coordinator == self]:
|
||||
if device.entity_id is not self.entity_id:
|
||||
self.hass.add_job(device.async_update_ha_state)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._queue is None and self.entity_id is not None:
|
||||
self._subscribe_to_player_events()
|
||||
|
@ -707,7 +682,7 @@ class SonosDevice(MediaPlayerDevice):
|
|||
self._player_volume_muted = \
|
||||
event.variables['mute'].get('Master') == '1'
|
||||
|
||||
self.update_ha_state(True)
|
||||
self.schedule_update_ha_state(True)
|
||||
|
||||
if next_track_image_url:
|
||||
self.preload_media_image_url(next_track_image_url)
|
||||
|
@ -946,37 +921,93 @@ class SonosDevice(MediaPlayerDevice):
|
|||
else:
|
||||
self._player.play_uri(media_id)
|
||||
|
||||
def group_players(self):
|
||||
"""Group all players under this coordinator."""
|
||||
if self._coordinator:
|
||||
self._coordinator.group_players()
|
||||
else:
|
||||
self._player.partymode()
|
||||
def join(self, master):
|
||||
"""Join the player to a group."""
|
||||
coord = [device.soco_device for device in self.hass.data[DATA_SONOS]
|
||||
if device.entity_id == master]
|
||||
|
||||
if coord and master != self.entity_id:
|
||||
self._player.join(coord[0])
|
||||
else:
|
||||
_LOGGER.error("Master not found %s", master)
|
||||
|
||||
@only_if_coordinator
|
||||
def unjoin(self):
|
||||
"""Unjoin the player from a group."""
|
||||
self._player.unjoin()
|
||||
|
||||
@only_if_coordinator
|
||||
def snapshot(self):
|
||||
def snapshot(self, with_group=True):
|
||||
"""Snapshot the player."""
|
||||
self.soco_snapshot.snapshot()
|
||||
|
||||
@only_if_coordinator
|
||||
def restore(self):
|
||||
"""Restore snapshot for the player."""
|
||||
self.soco_snapshot.restore(True)
|
||||
if with_group:
|
||||
self._snapshot_group = self._player.group
|
||||
if self._coordinator:
|
||||
self._coordinator.snapshot(False)
|
||||
else:
|
||||
self._snapshot_group = None
|
||||
|
||||
def restore(self, with_group=True):
|
||||
"""Restore snapshot for the player."""
|
||||
from soco.exceptions import SoCoException
|
||||
try:
|
||||
# need catch exception if a coordinator is going to slave.
|
||||
# this state will recover with group part.
|
||||
self.soco_snapshot.restore(True)
|
||||
except (TypeError, SoCoException):
|
||||
_LOGGER.debug("Error on restore %s", self.entity_id)
|
||||
|
||||
# restore groups
|
||||
if with_group and self._snapshot_group:
|
||||
old = self._snapshot_group
|
||||
actual = self._player.group
|
||||
|
||||
##
|
||||
# Master have not change, update group
|
||||
if old.coordinator == actual.coordinator:
|
||||
if self._player is not old.coordinator:
|
||||
# restore state of the groups
|
||||
self._coordinator.restore(False)
|
||||
remove = actual.members - old.members
|
||||
add = old.members - actual.members
|
||||
|
||||
# remove new members
|
||||
for soco_dev in list(remove):
|
||||
soco_dev.unjoin()
|
||||
|
||||
# add old members
|
||||
for soco_dev in list(add):
|
||||
soco_dev.join(old.coordinator)
|
||||
return
|
||||
|
||||
##
|
||||
# old is allready master, rejoin
|
||||
if old.coordinator.group.coordinator == old.coordinator:
|
||||
self._player.join(old.coordinator)
|
||||
return
|
||||
|
||||
##
|
||||
# restore old master, update group
|
||||
old.coordinator.unjoin()
|
||||
coordinator = _get_entity_from_soco(self.hass, old.coordinator)
|
||||
coordinator.restore(False)
|
||||
|
||||
for s_dev in list(old.members):
|
||||
if s_dev != old.coordinator:
|
||||
s_dev.join(old.coordinator)
|
||||
|
||||
@only_if_coordinator
|
||||
def set_sleep_timer(self, sleep_time):
|
||||
"""Set the timer on the player."""
|
||||
self._player.set_sleep_timer(sleep_time)
|
||||
if self._coordinator:
|
||||
self._coordinator.set_sleep_timer(sleep_time)
|
||||
else:
|
||||
self._player.set_sleep_timer(sleep_time)
|
||||
|
||||
@only_if_coordinator
|
||||
def clear_sleep_timer(self):
|
||||
"""Clear the timer on the player."""
|
||||
self._player.set_sleep_timer(None)
|
||||
if self._coordinator:
|
||||
self._coordinator.set_sleep_timer(None)
|
||||
else:
|
||||
self._player.set_sleep_timer(None)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
|
1
pylintrc
1
pylintrc
|
@ -30,6 +30,7 @@ disable=
|
|||
too-many-public-methods,
|
||||
too-many-return-statements,
|
||||
too-many-statements,
|
||||
too-many-lines,
|
||||
too-few-public-methods,
|
||||
abstract-method
|
||||
|
||||
|
|
|
@ -84,8 +84,8 @@ class SoCoMock():
|
|||
"""Return true if coordinator."""
|
||||
return True
|
||||
|
||||
def partymode(self):
|
||||
"""Cause the speaker to join all other speakers in the network."""
|
||||
def join(self, master):
|
||||
"""Join speaker to a group."""
|
||||
return
|
||||
|
||||
def set_sleep_timer(self, sleep_time_seconds):
|
||||
|
@ -100,6 +100,10 @@ class SoCoMock():
|
|||
"""Return a player uid."""
|
||||
return "RINCON_XXXXXXXXXXXXXXXXX"
|
||||
|
||||
def group(self):
|
||||
"""Return all group data of this player."""
|
||||
return
|
||||
|
||||
|
||||
def fake_add_device(devices, update_befor_add=False):
|
||||
"""Fake add device / update."""
|
||||
|
@ -129,7 +133,6 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
"""Stop everything that was started."""
|
||||
# Monkey patches
|
||||
sonos.SonosDevice.available = self.real_available
|
||||
sonos.DEVICES = []
|
||||
self.hass.stop()
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
|
@ -138,8 +141,8 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
"""Test a single device using the autodiscovery provided by HASS."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device, '192.0.2.1')
|
||||
|
||||
self.assertEqual(len(sonos.DEVICES), 1)
|
||||
self.assertEqual(sonos.DEVICES[0].name, 'Kitchen')
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 1)
|
||||
self.assertEqual(self.hass.data[sonos.DATA_SONOS][0].name, 'Kitchen')
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
||||
|
@ -157,7 +160,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
|
||||
assert setup_component(self.hass, DOMAIN, config)
|
||||
|
||||
self.assertEqual(len(sonos.DEVICES), 1)
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 1)
|
||||
self.assertEqual(discover_mock.call_count, 1)
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
|
@ -177,7 +180,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
|
||||
assert setup_component(self.hass, DOMAIN, config)
|
||||
|
||||
self.assertEqual(len(sonos.DEVICES), 1)
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 1)
|
||||
self.assertEqual(discover_mock.call_count, 1)
|
||||
self.assertEqual(soco.config.EVENT_ADVERTISE_IP, '192.0.1.1')
|
||||
|
||||
|
@ -194,8 +197,8 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
|
||||
assert setup_component(self.hass, DOMAIN, config)
|
||||
|
||||
self.assertEqual(len(sonos.DEVICES), 1)
|
||||
self.assertEqual(sonos.DEVICES[0].name, 'Kitchen')
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 1)
|
||||
self.assertEqual(self.hass.data[sonos.DATA_SONOS][0].name, 'Kitchen')
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
||||
|
@ -210,8 +213,8 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
|
||||
assert setup_component(self.hass, DOMAIN, config)
|
||||
|
||||
self.assertEqual(len(sonos.DEVICES), 2)
|
||||
self.assertEqual(sonos.DEVICES[0].name, 'Kitchen')
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 2)
|
||||
self.assertEqual(self.hass.data[sonos.DATA_SONOS][0].name, 'Kitchen')
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
||||
|
@ -226,8 +229,8 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
|
||||
assert setup_component(self.hass, DOMAIN, config)
|
||||
|
||||
self.assertEqual(len(sonos.DEVICES), 2)
|
||||
self.assertEqual(sonos.DEVICES[0].name, 'Kitchen')
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 2)
|
||||
self.assertEqual(self.hass.data[sonos.DATA_SONOS][0].name, 'Kitchen')
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
@mock.patch.object(soco, 'discover', new=socoDiscoverMock.discover)
|
||||
|
@ -235,20 +238,25 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
def test_ensure_setup_sonos_discovery(self, *args):
|
||||
"""Test a single device using the autodiscovery provided by Sonos."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device)
|
||||
self.assertEqual(len(sonos.DEVICES), 1)
|
||||
self.assertEqual(sonos.DEVICES[0].name, 'Kitchen')
|
||||
self.assertEqual(len(self.hass.data[sonos.DATA_SONOS]), 1)
|
||||
self.assertEqual(self.hass.data[sonos.DATA_SONOS][0].name, 'Kitchen')
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
||||
@mock.patch.object(SoCoMock, 'partymode')
|
||||
def test_sonos_group_players(self, partymodeMock, *args):
|
||||
@mock.patch.object(SoCoMock, 'join')
|
||||
def test_sonos_group_players(self, join_mock, *args):
|
||||
"""Ensuring soco methods called for sonos_group_players service."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device, '192.0.2.1')
|
||||
device = sonos.DEVICES[-1]
|
||||
partymodeMock.return_value = True
|
||||
device.group_players()
|
||||
self.assertEqual(partymodeMock.call_count, 1)
|
||||
self.assertEqual(partymodeMock.call_args, mock.call())
|
||||
device = self.hass.data[sonos.DATA_SONOS][-1]
|
||||
|
||||
device_master = mock.MagicMock()
|
||||
device_master.entity_id = "media_player.test"
|
||||
device_master.soco_device = device
|
||||
self.hass.data[sonos.DATA_SONOS].append(device_master)
|
||||
|
||||
join_mock.return_value = True
|
||||
device.join("media_player.test")
|
||||
self.assertEqual(join_mock.call_count, 1)
|
||||
|
||||
@mock.patch('soco.SoCo', new=SoCoMock)
|
||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
||||
|
@ -256,7 +264,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
def test_sonos_unjoin(self, unjoinMock, *args):
|
||||
"""Ensuring soco methods called for sonos_unjoin service."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device, '192.0.2.1')
|
||||
device = sonos.DEVICES[-1]
|
||||
device = self.hass.data[sonos.DATA_SONOS][-1]
|
||||
unjoinMock.return_value = True
|
||||
device.unjoin()
|
||||
self.assertEqual(unjoinMock.call_count, 1)
|
||||
|
@ -268,7 +276,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
def test_sonos_set_sleep_timer(self, set_sleep_timerMock, *args):
|
||||
"""Ensuring soco methods called for sonos_set_sleep_timer service."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device, '192.0.2.1')
|
||||
device = sonos.DEVICES[-1]
|
||||
device = self.hass.data[sonos.DATA_SONOS][-1]
|
||||
device.set_sleep_timer(30)
|
||||
set_sleep_timerMock.assert_called_once_with(30)
|
||||
|
||||
|
@ -278,7 +286,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
def test_sonos_clear_sleep_timer(self, set_sleep_timerMock, *args):
|
||||
"""Ensuring soco methods called for sonos_clear_sleep_timer service."""
|
||||
sonos.setup_platform(self.hass, {}, mock.MagicMock(), '192.0.2.1')
|
||||
device = sonos.DEVICES[-1]
|
||||
device = self.hass.data[sonos.DATA_SONOS][-1]
|
||||
device.set_sleep_timer(None)
|
||||
set_sleep_timerMock.assert_called_once_with(None)
|
||||
|
||||
|
@ -288,7 +296,7 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
def test_sonos_snapshot(self, snapshotMock, *args):
|
||||
"""Ensuring soco methods called for sonos_snapshot service."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device, '192.0.2.1')
|
||||
device = sonos.DEVICES[-1]
|
||||
device = self.hass.data[sonos.DATA_SONOS][-1]
|
||||
snapshotMock.return_value = True
|
||||
device.snapshot()
|
||||
self.assertEqual(snapshotMock.call_count, 1)
|
||||
|
@ -300,8 +308,10 @@ class TestSonosMediaPlayer(unittest.TestCase):
|
|||
def test_sonos_restore(self, restoreMock, *args):
|
||||
"""Ensuring soco methods called for sonos_restor service."""
|
||||
sonos.setup_platform(self.hass, {}, fake_add_device, '192.0.2.1')
|
||||
device = sonos.DEVICES[-1]
|
||||
device = self.hass.data[sonos.DATA_SONOS][-1]
|
||||
restoreMock.return_value = True
|
||||
device._snapshot_coordinator = mock.MagicMock()
|
||||
device._snapshot_coordinator.soco_device = SoCoMock('192.0.2.17')
|
||||
device.restore()
|
||||
self.assertEqual(restoreMock.call_count, 1)
|
||||
self.assertEqual(restoreMock.call_args, mock.call(True))
|
||||
|
|
Loading…
Add table
Reference in a new issue