mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Remove support for deprecated Sonos configuration (#23385)
This commit is contained in:
parent
d038d2426b
commit
5dbf58d67f
@ -1,10 +1,26 @@
|
|||||||
"""Support to embed Sonos."""
|
"""Support to embed Sonos."""
|
||||||
from homeassistant import config_entries
|
import voluptuous as vol
|
||||||
from homeassistant.helpers import config_entry_flow
|
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||||
|
from homeassistant.const import CONF_HOSTS
|
||||||
|
from homeassistant.helpers import config_entry_flow, config_validation as cv
|
||||||
|
|
||||||
DOMAIN = 'sonos'
|
DOMAIN = 'sonos'
|
||||||
|
|
||||||
|
CONF_ADVERTISE_ADDR = 'advertise_addr'
|
||||||
|
CONF_INTERFACE_ADDR = 'interface_addr'
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
MP_DOMAIN: vol.Schema({
|
||||||
|
vol.Optional(CONF_ADVERTISE_ADDR): cv.string,
|
||||||
|
vol.Optional(CONF_INTERFACE_ADDR): cv.string,
|
||||||
|
vol.Optional(CONF_HOSTS): vol.All(cv.ensure_list_csv, [cv.string]),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up the Sonos component."""
|
"""Set up the Sonos component."""
|
||||||
@ -22,7 +38,7 @@ async def async_setup(hass, config):
|
|||||||
async def async_setup_entry(hass, entry):
|
async def async_setup_entry(hass, entry):
|
||||||
"""Set up Sonos from a config entry."""
|
"""Set up Sonos from a config entry."""
|
||||||
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
|
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
|
||||||
entry, 'media_player'))
|
entry, MP_DOMAIN))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,20 +10,21 @@ import async_timeout
|
|||||||
import requests
|
import requests
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import MediaPlayerDevice
|
||||||
PLATFORM_SCHEMA, MediaPlayerDevice)
|
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST,
|
ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST,
|
||||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
|
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE,
|
||||||
SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
|
SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED,
|
ATTR_ENTITY_ID, ATTR_TIME, STATE_IDLE, STATE_OFF, STATE_PAUSED,
|
||||||
STATE_PLAYING)
|
STATE_PLAYING)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from . import DOMAIN as SONOS_DOMAIN
|
from . import (
|
||||||
|
CONF_ADVERTISE_ADDR, CONF_HOSTS, CONF_INTERFACE_ADDR,
|
||||||
|
DOMAIN as SONOS_DOMAIN)
|
||||||
|
|
||||||
DEPENDENCIES = ('sonos',)
|
DEPENDENCIES = ('sonos',)
|
||||||
|
|
||||||
@ -54,9 +55,6 @@ DATA_SONOS = 'sonos_media_player'
|
|||||||
SOURCE_LINEIN = 'Line-in'
|
SOURCE_LINEIN = 'Line-in'
|
||||||
SOURCE_TV = 'TV'
|
SOURCE_TV = 'TV'
|
||||||
|
|
||||||
CONF_ADVERTISE_ADDR = 'advertise_addr'
|
|
||||||
CONF_INTERFACE_ADDR = 'interface_addr'
|
|
||||||
|
|
||||||
# Service call validation schemas
|
# Service call validation schemas
|
||||||
ATTR_SLEEP_TIME = 'sleep_time'
|
ATTR_SLEEP_TIME = 'sleep_time'
|
||||||
ATTR_ALARM_ID = 'alarm_id'
|
ATTR_ALARM_ID = 'alarm_id'
|
||||||
@ -72,12 +70,6 @@ ATTR_SONOS_GROUP = 'sonos_group'
|
|||||||
|
|
||||||
UPNP_ERRORS_TO_IGNORE = ['701', '711', '712']
|
UPNP_ERRORS_TO_IGNORE = ['701', '711', '712']
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
||||||
vol.Optional(CONF_ADVERTISE_ADDR): cv.string,
|
|
||||||
vol.Optional(CONF_INTERFACE_ADDR): cv.string,
|
|
||||||
vol.Optional(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string]),
|
|
||||||
})
|
|
||||||
|
|
||||||
SONOS_SCHEMA = vol.Schema({
|
SONOS_SCHEMA = vol.Schema({
|
||||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
})
|
})
|
||||||
@ -119,57 +111,34 @@ class SonosData:
|
|||||||
self.topology_condition = asyncio.Condition(loop=hass.loop)
|
self.topology_condition = asyncio.Condition(loop=hass.loop)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass,
|
||||||
"""Set up the Sonos platform.
|
config,
|
||||||
|
async_add_entities,
|
||||||
Deprecated.
|
discovery_info=None):
|
||||||
"""
|
"""Set up the Sonos platform. Obsolete."""
|
||||||
_LOGGER.warning('Loading Sonos via platform config is deprecated.')
|
_LOGGER.error(
|
||||||
_setup_platform(hass, config, add_entities, discovery_info)
|
'Loading Sonos by media_player platform config is no longer supported')
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Sonos from a config entry."""
|
"""Set up Sonos from a config entry."""
|
||||||
def add_entities(entities, update_before_add=False):
|
|
||||||
"""Sync version of async add entities."""
|
|
||||||
hass.add_job(async_add_entities, entities, update_before_add)
|
|
||||||
|
|
||||||
hass.async_add_executor_job(
|
|
||||||
_setup_platform, hass, hass.data[SONOS_DOMAIN].get('media_player', {}),
|
|
||||||
add_entities, None)
|
|
||||||
|
|
||||||
|
|
||||||
def _setup_platform(hass, config, add_entities, discovery_info):
|
|
||||||
"""Set up the Sonos platform."""
|
|
||||||
import pysonos
|
import pysonos
|
||||||
|
|
||||||
if DATA_SONOS not in hass.data:
|
if DATA_SONOS not in hass.data:
|
||||||
hass.data[DATA_SONOS] = SonosData(hass)
|
hass.data[DATA_SONOS] = SonosData(hass)
|
||||||
|
|
||||||
|
config = hass.data[SONOS_DOMAIN].get('media_player', {})
|
||||||
|
|
||||||
advertise_addr = config.get(CONF_ADVERTISE_ADDR)
|
advertise_addr = config.get(CONF_ADVERTISE_ADDR)
|
||||||
if advertise_addr:
|
if advertise_addr:
|
||||||
pysonos.config.EVENT_ADVERTISE_IP = advertise_addr
|
pysonos.config.EVENT_ADVERTISE_IP = advertise_addr
|
||||||
|
|
||||||
players = []
|
def _create_sonos_entities():
|
||||||
if discovery_info:
|
"""Discover players and return a list of SonosEntity objects."""
|
||||||
player = pysonos.SoCo(discovery_info.get('host'))
|
players = []
|
||||||
|
|
||||||
# If host already exists by config
|
|
||||||
if player.uid in hass.data[DATA_SONOS].uids:
|
|
||||||
return
|
|
||||||
|
|
||||||
# If invisible, such as a stereo slave
|
|
||||||
if not player.is_visible:
|
|
||||||
return
|
|
||||||
|
|
||||||
players.append(player)
|
|
||||||
else:
|
|
||||||
hosts = config.get(CONF_HOSTS)
|
hosts = config.get(CONF_HOSTS)
|
||||||
|
|
||||||
if hosts:
|
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
|
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
try:
|
try:
|
||||||
players.append(pysonos.SoCo(socket.gethostbyname(host)))
|
players.append(pysonos.SoCo(socket.gethostbyname(host)))
|
||||||
@ -182,11 +151,14 @@ def _setup_platform(hass, config, add_entities, discovery_info):
|
|||||||
|
|
||||||
if not players:
|
if not players:
|
||||||
_LOGGER.warning("No Sonos speakers found")
|
_LOGGER.warning("No Sonos speakers found")
|
||||||
return
|
|
||||||
|
|
||||||
hass.data[DATA_SONOS].uids.update(p.uid for p in players)
|
return [SonosEntity(p) for p in players]
|
||||||
add_entities(SonosEntity(p) for p in players)
|
|
||||||
_LOGGER.debug("Added %s Sonos speakers", len(players))
|
entities = await hass.async_add_executor_job(_create_sonos_entities)
|
||||||
|
hass.data[DATA_SONOS].uids.update(e.unique_id for e in entities)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
_LOGGER.debug("Added %s Sonos speakers", len(entities))
|
||||||
|
|
||||||
def _service_to_entities(service):
|
def _service_to_entities(service):
|
||||||
"""Extract and return entities from service call."""
|
"""Extract and return entities from service call."""
|
||||||
@ -216,19 +188,19 @@ def _setup_platform(hass, config, add_entities, discovery_info):
|
|||||||
await SonosEntity.restore_multi(
|
await SonosEntity.restore_multi(
|
||||||
hass, entities, service.data[ATTR_WITH_GROUP])
|
hass, entities, service.data[ATTR_WITH_GROUP])
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_JOIN, async_service_handle,
|
DOMAIN, SERVICE_JOIN, async_service_handle,
|
||||||
schema=SONOS_JOIN_SCHEMA)
|
schema=SONOS_JOIN_SCHEMA)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_UNJOIN, async_service_handle,
|
DOMAIN, SERVICE_UNJOIN, async_service_handle,
|
||||||
schema=SONOS_SCHEMA)
|
schema=SONOS_SCHEMA)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_SNAPSHOT, async_service_handle,
|
DOMAIN, SERVICE_SNAPSHOT, async_service_handle,
|
||||||
schema=SONOS_STATES_SCHEMA)
|
schema=SONOS_STATES_SCHEMA)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_RESTORE, async_service_handle,
|
DOMAIN, SERVICE_RESTORE, async_service_handle,
|
||||||
schema=SONOS_STATES_SCHEMA)
|
schema=SONOS_STATES_SCHEMA)
|
||||||
|
|
||||||
@ -244,19 +216,19 @@ def _setup_platform(hass, config, add_entities, discovery_info):
|
|||||||
elif service.service == SERVICE_SET_OPTION:
|
elif service.service == SERVICE_SET_OPTION:
|
||||||
entity.set_option(**service.data)
|
entity.set_option(**service.data)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_SET_TIMER, service_handle,
|
DOMAIN, SERVICE_SET_TIMER, service_handle,
|
||||||
schema=SONOS_SET_TIMER_SCHEMA)
|
schema=SONOS_SET_TIMER_SCHEMA)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_CLEAR_TIMER, service_handle,
|
DOMAIN, SERVICE_CLEAR_TIMER, service_handle,
|
||||||
schema=SONOS_SCHEMA)
|
schema=SONOS_SCHEMA)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_UPDATE_ALARM, service_handle,
|
DOMAIN, SERVICE_UPDATE_ALARM, service_handle,
|
||||||
schema=SONOS_UPDATE_ALARM_SCHEMA)
|
schema=SONOS_UPDATE_ALARM_SCHEMA)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_SET_OPTION, service_handle,
|
DOMAIN, SERVICE_SET_OPTION, service_handle,
|
||||||
schema=SONOS_SET_OPTION_SCHEMA)
|
schema=SONOS_SET_OPTION_SCHEMA)
|
||||||
|
|
||||||
|
77
tests/components/sonos/conftest.py
Normal file
77
tests/components/sonos/conftest.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""Configuration for Sonos tests."""
|
||||||
|
from asynctest.mock import Mock, patch as patch
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.sonos import DOMAIN
|
||||||
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||||
|
from homeassistant.const import CONF_HOSTS
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="config_entry")
|
||||||
|
def config_entry_fixture():
|
||||||
|
"""Create a mock Sonos config entry."""
|
||||||
|
return MockConfigEntry(domain=DOMAIN, title='Sonos')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="soco")
|
||||||
|
def soco_fixture(music_library, speaker_info, dummy_soco_service):
|
||||||
|
"""Create a mock pysonos SoCo fixture."""
|
||||||
|
with patch('pysonos.SoCo', autospec=True) as mock, \
|
||||||
|
patch('socket.gethostbyname', return_value='192.168.42.2'):
|
||||||
|
mock_soco = mock.return_value
|
||||||
|
mock_soco.uid = 'RINCON_test'
|
||||||
|
mock_soco.music_library = music_library
|
||||||
|
mock_soco.get_speaker_info.return_value = speaker_info
|
||||||
|
mock_soco.avTransport = dummy_soco_service
|
||||||
|
mock_soco.renderingControl = dummy_soco_service
|
||||||
|
mock_soco.zoneGroupTopology = dummy_soco_service
|
||||||
|
mock_soco.contentDirectory = dummy_soco_service
|
||||||
|
|
||||||
|
yield mock_soco
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="discover")
|
||||||
|
def discover_fixture(soco):
|
||||||
|
"""Create a mock pysonos discover fixture."""
|
||||||
|
with patch('pysonos.discover') as mock:
|
||||||
|
mock.return_value = {soco}
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="config")
|
||||||
|
def config_fixture():
|
||||||
|
"""Create hass config fixture."""
|
||||||
|
return {
|
||||||
|
DOMAIN: {
|
||||||
|
MP_DOMAIN: {
|
||||||
|
CONF_HOSTS: ['192.168.42.1']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="dummy_soco_service")
|
||||||
|
def dummy_soco_service_fixture():
|
||||||
|
"""Create dummy_soco_service fixture."""
|
||||||
|
service = Mock()
|
||||||
|
service.subscribe = Mock()
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="music_library")
|
||||||
|
def music_library_fixture():
|
||||||
|
"""Create music_library fixture."""
|
||||||
|
music_library = Mock()
|
||||||
|
music_library.get_sonos_favorites.return_value = []
|
||||||
|
return music_library
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="speaker_info")
|
||||||
|
def speaker_info_fixture():
|
||||||
|
"""Create speaker_info fixture."""
|
||||||
|
return {
|
||||||
|
'zone_name': 'Zone A',
|
||||||
|
'model_name': 'Model Name',
|
||||||
|
}
|
@ -35,7 +35,9 @@ async def test_configuring_sonos_creates_entry(hass):
|
|||||||
patch('pysonos.discover', return_value=True):
|
patch('pysonos.discover', return_value=True):
|
||||||
await async_setup_component(hass, sonos.DOMAIN, {
|
await async_setup_component(hass, sonos.DOMAIN, {
|
||||||
'sonos': {
|
'sonos': {
|
||||||
'some_config': 'to_trigger_import'
|
'media_player': {
|
||||||
|
'interface_addr': '127.0.0.1',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -1,360 +1,22 @@
|
|||||||
"""The tests for the Demo Media player platform."""
|
"""Tests for the Sonos Media Player platform."""
|
||||||
import datetime
|
from homeassistant.components.sonos import media_player, DOMAIN
|
||||||
import socket
|
from homeassistant.setup import async_setup_component
|
||||||
import unittest
|
|
||||||
import pysonos.snapshot
|
|
||||||
from unittest import mock
|
|
||||||
import pysonos
|
|
||||||
from pysonos import alarms
|
|
||||||
|
|
||||||
from homeassistant.setup import setup_component
|
|
||||||
from homeassistant.components.sonos import media_player as sonos
|
|
||||||
from homeassistant.components.media_player.const import DOMAIN
|
|
||||||
from homeassistant.components.sonos.media_player import CONF_INTERFACE_ADDR
|
|
||||||
from homeassistant.const import CONF_HOSTS, CONF_PLATFORM
|
|
||||||
from homeassistant.util.async_ import run_coroutine_threadsafe
|
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
|
||||||
|
|
||||||
ENTITY_ID = 'media_player.kitchen'
|
|
||||||
|
|
||||||
|
|
||||||
class pysonosDiscoverMock():
|
async def setup_platform(hass, config_entry, config):
|
||||||
"""Mock class for the pysonos.discover method."""
|
"""Set up the media player platform for testing."""
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
def discover(interface_addr, all_households=False):
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
"""Return tuple of pysonos.SoCo objects representing found speakers."""
|
await hass.async_block_till_done()
|
||||||
return {SoCoMock('192.0.2.1')}
|
|
||||||
|
|
||||||
|
|
||||||
class AvTransportMock():
|
async def test_async_setup_entry_hosts(hass, config_entry, config, soco):
|
||||||
"""Mock class for the avTransport property on pysonos.SoCo object."""
|
"""Test static setup."""
|
||||||
|
await setup_platform(hass, config_entry, config)
|
||||||
def __init__(self):
|
assert hass.data[media_player.DATA_SONOS].entities[0].soco == soco
|
||||||
"""Initialize ethe Transport mock."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def GetMediaInfo(self, _):
|
|
||||||
"""Get the media details."""
|
|
||||||
return {
|
|
||||||
'CurrentURI': '',
|
|
||||||
'CurrentURIMetaData': ''
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MusicLibraryMock():
|
async def test_async_setup_entry_discover(hass, config_entry, discover):
|
||||||
"""Mock class for the music_library property on pysonos.SoCo object."""
|
"""Test discovery setup."""
|
||||||
|
await setup_platform(hass, config_entry, {})
|
||||||
def get_sonos_favorites(self):
|
assert hass.data[media_player.DATA_SONOS].uids == {'RINCON_test'}
|
||||||
"""Return favorites."""
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class CacheMock():
|
|
||||||
"""Mock class for the _zgs_cache property on pysonos.SoCo object."""
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Clear cache."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SoCoMock():
|
|
||||||
"""Mock class for the pysonos.SoCo object."""
|
|
||||||
|
|
||||||
def __init__(self, ip):
|
|
||||||
"""Initialize SoCo object."""
|
|
||||||
self.ip_address = ip
|
|
||||||
self.is_visible = True
|
|
||||||
self.volume = 50
|
|
||||||
self.mute = False
|
|
||||||
self.shuffle = False
|
|
||||||
self.night_mode = False
|
|
||||||
self.dialog_mode = False
|
|
||||||
self.music_library = MusicLibraryMock()
|
|
||||||
self.avTransport = AvTransportMock()
|
|
||||||
self._zgs_cache = CacheMock()
|
|
||||||
|
|
||||||
def get_sonos_favorites(self):
|
|
||||||
"""Get favorites list from sonos."""
|
|
||||||
return {'favorites': []}
|
|
||||||
|
|
||||||
def get_speaker_info(self, force):
|
|
||||||
"""Return a dict with various data points about the speaker."""
|
|
||||||
return {'serial_number': 'B8-E9-37-BO-OC-BA:2',
|
|
||||||
'software_version': '32.11-30071',
|
|
||||||
'uid': 'RINCON_B8E937BOOCBA02500',
|
|
||||||
'zone_icon': 'x-rincon-roomicon:kitchen',
|
|
||||||
'mac_address': 'B8:E9:37:BO:OC:BA',
|
|
||||||
'zone_name': 'Kitchen',
|
|
||||||
'model_name': 'Sonos PLAY:1',
|
|
||||||
'hardware_version': '1.8.1.2-1'}
|
|
||||||
|
|
||||||
def get_current_transport_info(self):
|
|
||||||
"""Return a dict with the current state of the speaker."""
|
|
||||||
return {'current_transport_speed': '1',
|
|
||||||
'current_transport_state': 'STOPPED',
|
|
||||||
'current_transport_status': 'OK'}
|
|
||||||
|
|
||||||
def get_current_track_info(self):
|
|
||||||
"""Return a dict with the current track information."""
|
|
||||||
return {'album': '',
|
|
||||||
'uri': '',
|
|
||||||
'title': '',
|
|
||||||
'artist': '',
|
|
||||||
'duration': '0:00:00',
|
|
||||||
'album_art': '',
|
|
||||||
'position': '0:00:00',
|
|
||||||
'playlist_position': '0',
|
|
||||||
'metadata': ''}
|
|
||||||
|
|
||||||
def is_coordinator(self):
|
|
||||||
"""Return true if coordinator."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def join(self, master):
|
|
||||||
"""Join speaker to a group."""
|
|
||||||
return
|
|
||||||
|
|
||||||
def set_sleep_timer(self, sleep_time_seconds):
|
|
||||||
"""Set the sleep timer."""
|
|
||||||
return
|
|
||||||
|
|
||||||
def unjoin(self):
|
|
||||||
"""Cause the speaker to separate itself from other speakers."""
|
|
||||||
return
|
|
||||||
|
|
||||||
def uid(self):
|
|
||||||
"""Return a player uid."""
|
|
||||||
return "RINCON_XXXXXXXXXXXXXXXXX"
|
|
||||||
|
|
||||||
def group(self):
|
|
||||||
"""Return all group data of this player."""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def add_entities_factory(hass):
|
|
||||||
"""Add entities factory."""
|
|
||||||
def add_entities(entities, update_befor_add=False):
|
|
||||||
"""Fake add entity."""
|
|
||||||
hass.data[sonos.DATA_SONOS].entities = list(entities)
|
|
||||||
|
|
||||||
return add_entities
|
|
||||||
|
|
||||||
|
|
||||||
class TestSonosMediaPlayer(unittest.TestCase):
|
|
||||||
"""Test the media_player module."""
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up things to be run when tests are started."""
|
|
||||||
self.hass = get_test_home_assistant()
|
|
||||||
|
|
||||||
def monkey_available(self):
|
|
||||||
"""Make a monkey available."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Monkey patches
|
|
||||||
self.real_available = sonos.SonosEntity.available
|
|
||||||
sonos.SonosEntity.available = monkey_available
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
def tearDown(self):
|
|
||||||
"""Stop everything that was started."""
|
|
||||||
# Monkey patches
|
|
||||||
sonos.SonosEntity.available = self.real_available
|
|
||||||
self.hass.stop()
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
def test_ensure_setup_discovery(self, *args):
|
|
||||||
"""Test a single device using the autodiscovery provided by HASS."""
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), {
|
|
||||||
'host': '192.0.2.1'
|
|
||||||
})
|
|
||||||
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
assert len(entities) == 1
|
|
||||||
assert entities[0].name == 'Kitchen'
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
@mock.patch('pysonos.discover')
|
|
||||||
def test_ensure_setup_config_interface_addr(self, discover_mock, *args):
|
|
||||||
"""Test an interface address config'd by the HASS config file."""
|
|
||||||
discover_mock.return_value = {SoCoMock('192.0.2.1')}
|
|
||||||
|
|
||||||
config = {
|
|
||||||
DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'sonos',
|
|
||||||
CONF_INTERFACE_ADDR: '192.0.1.1',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert setup_component(self.hass, DOMAIN, config)
|
|
||||||
|
|
||||||
assert len(self.hass.data[sonos.DATA_SONOS].entities) == 1
|
|
||||||
assert discover_mock.call_count == 1
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
def test_ensure_setup_config_hosts_string_single(self, *args):
|
|
||||||
"""Test a single address config'd by the HASS config file."""
|
|
||||||
config = {
|
|
||||||
DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'sonos',
|
|
||||||
CONF_HOSTS: ['192.0.2.1'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert setup_component(self.hass, DOMAIN, config)
|
|
||||||
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
assert len(entities) == 1
|
|
||||||
assert entities[0].name == 'Kitchen'
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
def test_ensure_setup_config_hosts_string_multiple(self, *args):
|
|
||||||
"""Test multiple address string config'd by the HASS config file."""
|
|
||||||
config = {
|
|
||||||
DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'sonos',
|
|
||||||
CONF_HOSTS: ['192.0.2.1,192.168.2.2'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert setup_component(self.hass, DOMAIN, config)
|
|
||||||
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
assert len(entities) == 2
|
|
||||||
assert entities[0].name == 'Kitchen'
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
def test_ensure_setup_config_hosts_list(self, *args):
|
|
||||||
"""Test a multiple address list config'd by the HASS config file."""
|
|
||||||
config = {
|
|
||||||
DOMAIN: {
|
|
||||||
CONF_PLATFORM: 'sonos',
|
|
||||||
CONF_HOSTS: ['192.0.2.1', '192.168.2.2'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert setup_component(self.hass, DOMAIN, config)
|
|
||||||
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
assert len(entities) == 2
|
|
||||||
assert entities[0].name == 'Kitchen'
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch.object(pysonos, 'discover', new=pysonosDiscoverMock.discover)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
def test_ensure_setup_sonos_discovery(self, *args):
|
|
||||||
"""Test a single device using the autodiscovery provided by Sonos."""
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass))
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
assert len(entities) == 1
|
|
||||||
assert entities[0].name == 'Kitchen'
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
@mock.patch.object(SoCoMock, 'set_sleep_timer')
|
|
||||||
def test_sonos_set_sleep_timer(self, set_sleep_timerMock, *args):
|
|
||||||
"""Ensure pysonos methods called for sonos_set_sleep_timer service."""
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), {
|
|
||||||
'host': '192.0.2.1'
|
|
||||||
})
|
|
||||||
entity = self.hass.data[sonos.DATA_SONOS].entities[-1]
|
|
||||||
entity.hass = self.hass
|
|
||||||
|
|
||||||
entity.set_sleep_timer(30)
|
|
||||||
set_sleep_timerMock.assert_called_once_with(30)
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
@mock.patch.object(SoCoMock, 'set_sleep_timer')
|
|
||||||
def test_sonos_clear_sleep_timer(self, set_sleep_timerMock, *args):
|
|
||||||
"""Ensure pysonos method called for sonos_clear_sleep_timer service."""
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), {
|
|
||||||
'host': '192.0.2.1'
|
|
||||||
})
|
|
||||||
entity = self.hass.data[sonos.DATA_SONOS].entities[-1]
|
|
||||||
entity.hass = self.hass
|
|
||||||
|
|
||||||
entity.set_sleep_timer(None)
|
|
||||||
set_sleep_timerMock.assert_called_once_with(None)
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('pysonos.alarms.Alarm')
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
def test_set_alarm(self, pysonos_mock, alarm_mock, *args):
|
|
||||||
"""Ensure pysonos methods called for sonos_set_sleep_timer service."""
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), {
|
|
||||||
'host': '192.0.2.1'
|
|
||||||
})
|
|
||||||
entity = self.hass.data[sonos.DATA_SONOS].entities[-1]
|
|
||||||
entity.hass = self.hass
|
|
||||||
alarm1 = alarms.Alarm(pysonos_mock)
|
|
||||||
alarm1.configure_mock(_alarm_id="1", start_time=None, enabled=False,
|
|
||||||
include_linked_zones=False, volume=100)
|
|
||||||
with mock.patch('pysonos.alarms.get_alarms', return_value=[alarm1]):
|
|
||||||
attrs = {
|
|
||||||
'time': datetime.time(12, 00),
|
|
||||||
'enabled': True,
|
|
||||||
'include_linked_zones': True,
|
|
||||||
'volume': 0.30,
|
|
||||||
}
|
|
||||||
entity.set_alarm(alarm_id=2)
|
|
||||||
alarm1.save.assert_not_called()
|
|
||||||
entity.set_alarm(alarm_id=1, **attrs)
|
|
||||||
assert alarm1.enabled == attrs['enabled']
|
|
||||||
assert alarm1.start_time == attrs['time']
|
|
||||||
assert alarm1.include_linked_zones == \
|
|
||||||
attrs['include_linked_zones']
|
|
||||||
assert alarm1.volume == 30
|
|
||||||
alarm1.save.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
@mock.patch.object(pysonos.snapshot.Snapshot, 'snapshot')
|
|
||||||
def test_sonos_snapshot(self, snapshotMock, *args):
|
|
||||||
"""Ensure pysonos methods called for sonos_snapshot service."""
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), {
|
|
||||||
'host': '192.0.2.1'
|
|
||||||
})
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
entity = entities[-1]
|
|
||||||
entity.hass = self.hass
|
|
||||||
|
|
||||||
snapshotMock.return_value = True
|
|
||||||
entity.soco.group = mock.MagicMock()
|
|
||||||
entity.soco.group.members = [e.soco for e in entities]
|
|
||||||
run_coroutine_threadsafe(
|
|
||||||
sonos.SonosEntity.snapshot_multi(self.hass, entities, True),
|
|
||||||
self.hass.loop).result()
|
|
||||||
assert snapshotMock.call_count == 1
|
|
||||||
assert snapshotMock.call_args == mock.call()
|
|
||||||
|
|
||||||
@mock.patch('pysonos.SoCo', new=SoCoMock)
|
|
||||||
@mock.patch('socket.create_connection', side_effect=socket.error())
|
|
||||||
@mock.patch.object(pysonos.snapshot.Snapshot, 'restore')
|
|
||||||
def test_sonos_restore(self, restoreMock, *args):
|
|
||||||
"""Ensure pysonos methods called for sonos_restore service."""
|
|
||||||
from pysonos.snapshot import Snapshot
|
|
||||||
|
|
||||||
sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), {
|
|
||||||
'host': '192.0.2.1'
|
|
||||||
})
|
|
||||||
entities = self.hass.data[sonos.DATA_SONOS].entities
|
|
||||||
entity = entities[-1]
|
|
||||||
entity.hass = self.hass
|
|
||||||
|
|
||||||
restoreMock.return_value = True
|
|
||||||
entity._snapshot_group = mock.MagicMock()
|
|
||||||
entity._snapshot_group.members = [e.soco for e in entities]
|
|
||||||
entity._soco_snapshot = Snapshot(entity.soco)
|
|
||||||
run_coroutine_threadsafe(
|
|
||||||
sonos.SonosEntity.restore_multi(self.hass, entities, True),
|
|
||||||
self.hass.loop).result()
|
|
||||||
assert restoreMock.call_count == 1
|
|
||||||
assert restoreMock.call_args == mock.call()
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user