Add Bose soundtouch discovery support and upgrade libsoundtouch library (#7005)

* Add Bose soundtouch discovery support and upgrade libsoundtouch library

* Remove DEVICE global variable

* Update netdisco to lastest version
This commit is contained in:
Charles Blonde 2017-04-20 06:52:37 +02:00 committed by Paulus Schoutsen
parent 76d2154820
commit 931fce8239
5 changed files with 341 additions and 274 deletions

View File

@ -50,6 +50,7 @@ SERVICE_HANDLERS = {
'apple_tv': ('media_player', 'apple_tv'),
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
'bose_soundtouch': ('media_player', 'soundtouch'),
}
CONF_IGNORE = 'ignore'

View File

@ -222,30 +222,39 @@ soundtouch_play_everywhere:
description: Play on all Bose Soundtouch devices
fields:
entity_id:
description: Name of entites that will coordinate the grouping. Platform dependent. It is a shortcut for creating a multi-room zone with all devices
master:
description: Name of the master entity that will coordinate the grouping. Platform dependent. It is a shortcut for creating a multi-room zone with all devices
example: 'media_player.soundtouch_home'
soundtouch_create_zone:
description: Create a multi-room zone
fields:
entity_id:
description: Name of entites that will coordinate the multi-room zone. Platform dependent.
master:
description: Name of the master entity that will coordinate the multi-room zone. Platform dependent.
example: 'media_player.soundtouch_home'
slaves:
description: Name of slaves entities to add to the new zone
example: 'media_player.soundtouch_bedroom'
soundtouch_add_zone_slave:
description: Add a slave to a multi-room zone
fields:
entity_id:
description: Name of entites that will be added to the multi-room zone. Platform dependent.
master:
description: Name of the master entity that is coordinating the multi-room zone. Platform dependent.
example: 'media_player.soundtouch_home'
slaves:
description: Name of slaves entities to add to the existing zone
example: 'media_player.soundtouch_bedroom'
soundtouch_remove_zone_slave:
description: Remove a slave from the multi-room zone
fields:
entity_id:
description: Name of entites that will be remove from the multi-room zone. Platform dependent.
master:
description: Name of the master entity that is coordinating the multi-room zone. Platform dependent.
example: 'media_player.soundtouch_home'
slaves:
description: Name of slaves entities to remove from the existing zone
example: 'media_player.soundtouch_bedroom'

View File

@ -15,7 +15,7 @@ from homeassistant.const import (CONF_HOST, CONF_NAME, STATE_OFF, CONF_PORT,
STATE_PAUSED, STATE_PLAYING,
STATE_UNAVAILABLE)
REQUIREMENTS = ['libsoundtouch==0.1.0']
REQUIREMENTS = ['libsoundtouch==0.3.0']
_LOGGER = logging.getLogger(__name__)
@ -29,33 +29,33 @@ MAP_STATUS = {
"PLAY_STATE": STATE_PLAYING,
"BUFFERING_STATE": STATE_PLAYING,
"PAUSE_STATE": STATE_PAUSED,
"STOp_STATE": STATE_OFF
"STOP_STATE": STATE_OFF
}
DATA_SOUNDTOUCH = "soundtouch"
SOUNDTOUCH_PLAY_EVERYWHERE = vol.Schema({
'master': cv.entity_id,
vol.Required('master'): cv.entity_id
})
SOUNDTOUCH_CREATE_ZONE_SCHEMA = vol.Schema({
'master': cv.entity_id,
'slaves': cv.entity_ids
vol.Required('master'): cv.entity_id,
vol.Required('slaves'): cv.entity_ids
})
SOUNDTOUCH_ADD_ZONE_SCHEMA = vol.Schema({
'master': cv.entity_id,
'slaves': cv.entity_ids
vol.Required('master'): cv.entity_id,
vol.Required('slaves'): cv.entity_ids
})
SOUNDTOUCH_REMOVE_ZONE_SCHEMA = vol.Schema({
'master': cv.entity_id,
'slaves': cv.entity_ids
vol.Required('master'): cv.entity_id,
vol.Required('slaves'): cv.entity_ids
})
DEFAULT_NAME = 'Bose Soundtouch'
DEFAULT_PORT = 8090
DEVICES = []
SUPPORT_SOUNDTOUCH = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
@ -70,180 +70,99 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Bose Soundtouch platform."""
name = config.get(CONF_NAME)
if DATA_SOUNDTOUCH not in hass.data:
hass.data[DATA_SOUNDTOUCH] = []
remote_config = {
'name': 'HomeAssistant',
'description': config.get(CONF_NAME),
'id': 'ha.component.soundtouch',
'port': config.get(CONF_PORT),
'host': config.get(CONF_HOST)
}
if discovery_info:
# Discovery
host = discovery_info["host"]
port = int(discovery_info["port"])
soundtouch_device = SoundTouchDevice(name, remote_config)
DEVICES.append(soundtouch_device)
add_devices([soundtouch_device])
# if device already exists by config
if host in [device.config['host'] for device in
hass.data[DATA_SOUNDTOUCH]]:
return
remote_config = {
'id': 'ha.component.soundtouch',
'host': host,
'port': port
}
soundtouch_device = SoundTouchDevice(None, remote_config)
hass.data[DATA_SOUNDTOUCH].append(soundtouch_device)
add_devices([soundtouch_device])
else:
# Config
name = config.get(CONF_NAME)
remote_config = {
'id': 'ha.component.soundtouch',
'port': config.get(CONF_PORT),
'host': config.get(CONF_HOST)
}
soundtouch_device = SoundTouchDevice(name, remote_config)
hass.data[DATA_SOUNDTOUCH].append(soundtouch_device)
add_devices([soundtouch_device])
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
def service_handle(service):
"""Internal func for applying a service."""
master_device_id = service.data.get('master')
slaves_ids = service.data.get('slaves')
slaves = []
if slaves_ids:
slaves = [device for device in hass.data[DATA_SOUNDTOUCH] if
device.entity_id in slaves_ids]
master = next([device for device in hass.data[DATA_SOUNDTOUCH] if
device.entity_id == master_device_id].__iter__(), None)
if master is None:
_LOGGER.warning("Unable to find master with entity_id:" + str(
master_device_id))
return
if service.service == SERVICE_PLAY_EVERYWHERE:
slaves = [d for d in hass.data[DATA_SOUNDTOUCH] if
d.entity_id != master_device_id]
master.create_zone(slaves)
elif service.service == SERVICE_CREATE_ZONE:
master.create_zone(slaves)
elif service.service == SERVICE_REMOVE_ZONE_SLAVE:
master.remove_zone_slave(slaves)
elif service.service == SERVICE_ADD_ZONE_SLAVE:
master.add_zone_slave(slaves)
hass.services.register(DOMAIN, SERVICE_PLAY_EVERYWHERE,
play_everywhere_service,
service_handle,
descriptions.get(SERVICE_PLAY_EVERYWHERE),
schema=SOUNDTOUCH_PLAY_EVERYWHERE)
hass.services.register(DOMAIN, SERVICE_CREATE_ZONE,
create_zone_service,
service_handle,
descriptions.get(SERVICE_CREATE_ZONE),
schema=SOUNDTOUCH_CREATE_ZONE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_REMOVE_ZONE_SLAVE,
remove_zone_slave,
service_handle,
descriptions.get(SERVICE_REMOVE_ZONE_SLAVE),
schema=SOUNDTOUCH_REMOVE_ZONE_SCHEMA)
hass.services.register(DOMAIN, SERVICE_ADD_ZONE_SLAVE,
add_zone_slave,
service_handle,
descriptions.get(SERVICE_ADD_ZONE_SLAVE),
schema=SOUNDTOUCH_ADD_ZONE_SCHEMA)
def play_everywhere_service(service):
"""
Create a zone (multi-room) and play on all devices.
:param service: Home Assistant service with 'master' data set
:Example:
- service: media_player.soundtouch_play_everywhere
data:
master: media_player.soundtouch_living_room
"""
master_device_id = service.data.get('master')
slaves = [d for d in DEVICES if d.entity_id != master_device_id]
master = next([device for device in DEVICES if
device.entity_id == master_device_id].__iter__(), None)
if master is None:
_LOGGER.warning(
"Unable to find master with entity_id:" + str(master_device_id))
elif not slaves:
_LOGGER.warning("Unable to create zone without slaves")
else:
_LOGGER.info(
"Creating zone with master " + str(master.device.config.name))
master.device.create_zone([slave.device for slave in slaves])
def create_zone_service(service):
"""
Create a zone (multi-room) on a master and play on specified slaves.
At least one master and one slave must be specified
:param service: Home Assistant service with 'master' and 'slaves' data set
:Example:
- service: media_player.soundtouch_create_zone
data:
master: media_player.soundtouch_living_room
slaves:
- media_player.soundtouch_room
- media_player.soundtouch_kitchen
"""
master_device_id = service.data.get('master')
slaves_ids = service.data.get('slaves')
slaves = [device for device in DEVICES if device.entity_id in slaves_ids]
master = next([device for device in DEVICES if
device.entity_id == master_device_id].__iter__(), None)
if master is None:
_LOGGER.warning(
"Unable to find master with entity_id:" + master_device_id)
elif not slaves:
_LOGGER.warning("Unable to create zone without slaves")
else:
_LOGGER.info(
"Creating zone with master " + str(master.device.config.name))
master.device.create_zone([slave.device for slave in slaves])
def add_zone_slave(service):
"""
Add slave(s) to and existing zone (multi-room).
Zone must already exist and slaves array can not be empty.
:param service: Home Assistant service with 'master' and 'slaves' data set
:Example:
- service: media_player.soundtouch_add_zone_slave
data:
master: media_player.soundtouch_living_room
slaves:
- media_player.soundtouch_room
"""
master_device_id = service.data.get('master')
slaves_ids = service.data.get('slaves')
slaves = [device for device in DEVICES if device.entity_id in slaves_ids]
master = next([device for device in DEVICES if
device.entity_id == master_device_id].__iter__(), None)
if master is None:
_LOGGER.warning(
"Unable to find master with entity_id:" + str(master_device_id))
elif not slaves:
_LOGGER.warning("Unable to find slaves to add")
else:
_LOGGER.info(
"Adding slaves to zone with master " + str(
master.device.config.name))
master.device.add_zone_slave([slave.device for slave in slaves])
def remove_zone_slave(service):
"""
Remove slave(s) from and existing zone (multi-room).
Zone must already exist and slaves array can not be empty.
Note: If removing last slave, the zone will be deleted and you'll have to
create a new one. You will not be able to add a new slave anymore
:param service: Home Assistant service with 'master' and 'slaves' data set
:Example:
- service: media_player.soundtouch_remove_zone_slave
data:
master: media_player.soundtouch_living_room
slaves:
- media_player.soundtouch_room
"""
master_device_id = service.data.get('master')
slaves_ids = service.data.get('slaves')
slaves = [device for device in DEVICES if device.entity_id in slaves_ids]
master = next([device for device in DEVICES if
device.entity_id == master_device_id].__iter__(), None)
if master is None:
_LOGGER.warning(
"Unable to find master with entity_id:" + master_device_id)
elif not slaves:
_LOGGER.warning("Unable to find slaves to remove")
else:
_LOGGER.info("Removing slaves from zone with master " +
str(master.device.config.name))
master.device.remove_zone_slave([slave.device for slave in slaves])
class SoundTouchDevice(MediaPlayerDevice):
"""Representation of a SoundTouch Bose device."""
def __init__(self, name, config):
"""Create Soundtouch Entity."""
from libsoundtouch import soundtouch_device
self._name = name
self._device = soundtouch_device(config['host'], config['port'])
if name is None:
self._name = self._device.config.name
else:
self._name = name
self._status = self._device.status()
self._volume = self._device.volume()
self._config = config
@ -297,7 +216,7 @@ class SoundTouchDevice(MediaPlayerDevice):
self._status = self._device.status()
def turn_on(self):
"""Turn the media player on."""
"""Turn on media player."""
self._device.power_on()
self._status = self._device.status()
@ -392,3 +311,52 @@ class SoundTouchDevice(MediaPlayerDevice):
self._device.select_preset(preset)
else:
_LOGGER.warning("Unable to find preset with id " + str(media_id))
def create_zone(self, slaves):
"""
Create a zone (multi-room) and play on selected devices.
:param slaves: slaves on which to play
"""
if not slaves:
_LOGGER.warning("Unable to create zone without slaves")
else:
_LOGGER.info(
"Creating zone with master " + str(self.device.config.name))
self.device.create_zone([slave.device for slave in slaves])
def remove_zone_slave(self, slaves):
"""
Remove slave(s) from and existing zone (multi-room).
Zone must already exist and slaves array can not be empty.
Note: If removing last slave, the zone will be deleted and you'll have
to create a new one. You will not be able to add a new slave anymore
:param slaves: slaves to remove from the zone
"""
if not slaves:
_LOGGER.warning("Unable to find slaves to remove")
else:
_LOGGER.info("Removing slaves from zone with master " +
str(self.device.config.name))
self.device.remove_zone_slave([slave.device for slave in slaves])
def add_zone_slave(self, slaves):
"""
Add slave(s) to and existing zone (multi-room).
Zone must already exist and slaves array can not be empty.
:param slaves:slaves to add
"""
if not slaves:
_LOGGER.warning("Unable to find slaves to add")
else:
_LOGGER.info(
"Adding slaves to zone with master " + str(
self.device.config.name))
self.device.add_zone_slave([slave.device for slave in slaves])

View File

@ -358,7 +358,7 @@ knxip==0.3.3
libnacl==1.5.0
# homeassistant.components.media_player.soundtouch
libsoundtouch==0.1.0
libsoundtouch==0.3.0
# homeassistant.components.light.lifx_legacy
liffylights==0.9.4

View File

@ -155,21 +155,67 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
logging.disable(logging.NOTSET)
soundtouch.DEVICES = []
self.hass.stop()
@mock.patch('libsoundtouch.soundtouch_device', side_effect=None)
def test_ensure_setup_config(self, mocked_sountouch_device):
"""Test setup OK."""
"""Test setup OK with custom config."""
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
# soundtouch.DEVICES[0].entity_id = 'entity_1'
self.assertEqual(len(soundtouch.DEVICES), 1)
self.assertEqual(soundtouch.DEVICES[0].name, 'soundtouch')
self.assertEqual(soundtouch.DEVICES[0].config['port'], 8090)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(len(all_devices), 1)
self.assertEqual(all_devices[0].name, 'soundtouch')
self.assertEqual(all_devices[0].config['port'], 8090)
self.assertEqual(mocked_sountouch_device.call_count, 1)
@mock.patch('libsoundtouch.soundtouch_device', side_effect=None)
def test_ensure_setup_discovery(self, mocked_sountouch_device):
"""Test setup with discovery."""
new_device = {"port": "8090",
"host": "192.168.1.1",
"properties": {},
"hostname": "hostname.local"}
soundtouch.setup_platform(self.hass,
None,
mock.MagicMock(),
new_device)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(len(all_devices), 1)
self.assertEqual(all_devices[0].config['port'], 8090)
self.assertEqual(all_devices[0].config['host'], '192.168.1.1')
self.assertEqual(mocked_sountouch_device.call_count, 1)
@mock.patch('libsoundtouch.soundtouch_device', side_effect=None)
def test_ensure_setup_discovery_no_duplicate(self,
mocked_sountouch_device):
"""Test setup OK if device already exists."""
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
self.assertEqual(len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]), 1)
new_device = {"port": "8090",
"host": "192.168.1.1",
"properties": {},
"hostname": "hostname.local"}
soundtouch.setup_platform(self.hass,
None,
mock.MagicMock(),
new_device # New device
)
self.assertEqual(len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]), 2)
existing_device = {"port": "8090",
"host": "192.168.0.1",
"properties": {},
"hostname": "hostname.local"}
soundtouch.setup_platform(self.hass,
None,
mock.MagicMock(),
existing_device # Existing device
)
self.assertEqual(mocked_sountouch_device.call_count, 2)
self.assertEqual(len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]), 2)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status')
@mock.patch('libsoundtouch.soundtouch_device',
@ -183,7 +229,7 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
soundtouch.DEVICES[0].update()
self.hass.data[soundtouch.DATA_SOUNDTOUCH][0].update()
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 2)
@ -201,13 +247,14 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].state, STATE_PLAYING)
self.assertEqual(soundtouch.DEVICES[0].media_image_url, "image.url")
self.assertEqual(soundtouch.DEVICES[0].media_title, "artist - track")
self.assertEqual(soundtouch.DEVICES[0].media_track, "track")
self.assertEqual(soundtouch.DEVICES[0].media_artist, "artist")
self.assertEqual(soundtouch.DEVICES[0].media_album_name, "album")
self.assertEqual(soundtouch.DEVICES[0].media_duration, 1)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].state, STATE_PLAYING)
self.assertEqual(all_devices[0].media_image_url, "image.url")
self.assertEqual(all_devices[0].media_title, "artist - track")
self.assertEqual(all_devices[0].media_track, "track")
self.assertEqual(all_devices[0].media_artist, "artist")
self.assertEqual(all_devices[0].media_album_name, "album")
self.assertEqual(all_devices[0].media_duration, 1)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status',
@ -223,7 +270,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].media_title, None)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].media_title, None)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status',
@ -239,13 +287,14 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].state, STATE_PLAYING)
self.assertEqual(soundtouch.DEVICES[0].media_image_url, "image.url")
self.assertEqual(soundtouch.DEVICES[0].media_title, "station")
self.assertEqual(soundtouch.DEVICES[0].media_track, None)
self.assertEqual(soundtouch.DEVICES[0].media_artist, None)
self.assertEqual(soundtouch.DEVICES[0].media_album_name, None)
self.assertEqual(soundtouch.DEVICES[0].media_duration, None)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].state, STATE_PLAYING)
self.assertEqual(all_devices[0].media_image_url, "image.url")
self.assertEqual(all_devices[0].media_title, "station")
self.assertEqual(all_devices[0].media_track, None)
self.assertEqual(all_devices[0].media_artist, None)
self.assertEqual(all_devices[0].media_album_name, None)
self.assertEqual(all_devices[0].media_duration, None)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume',
side_effect=MockVolume)
@ -261,7 +310,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].volume_level, 0.12)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].volume_level, 0.12)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status',
@ -277,7 +327,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].state, STATE_OFF)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].state, STATE_OFF)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status',
@ -293,7 +344,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].state, STATE_PAUSED)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].state, STATE_PAUSED)
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume',
side_effect=MockVolumeMuted)
@ -309,7 +361,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].is_volume_muted, True)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].is_volume_muted, True)
@mock.patch('libsoundtouch.soundtouch_device')
def test_media_commands(self, mocked_sountouch_device):
@ -318,7 +371,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
default_component(),
mock.MagicMock())
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(soundtouch.DEVICES[0].supported_features, 17853)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(all_devices[0].supported_features, 17853)
@mock.patch('libsoundtouch.device.SoundTouchDevice.power_off')
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@ -331,7 +385,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].turn_off()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].turn_off()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 1)
@ -348,7 +403,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].turn_on()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].turn_on()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 1)
@ -365,7 +421,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].volume_up()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].volume_up()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 2)
@ -382,7 +439,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].volume_down()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].volume_down()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 2)
@ -399,7 +457,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].set_volume_level(0.17)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].set_volume_level(0.17)
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 2)
@ -416,7 +475,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].mute_volume(None)
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].mute_volume(None)
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 2)
@ -433,7 +493,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].media_play()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].media_play()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 1)
@ -450,7 +511,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].media_pause()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].media_pause()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 1)
@ -467,7 +529,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].media_play_pause()
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].media_play_pause()
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 1)
@ -486,13 +549,14 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
soundtouch.DEVICES[0].media_next_track()
all_devices[0].media_next_track()
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_next_track.call_count, 1)
soundtouch.DEVICES[0].media_previous_track()
all_devices[0].media_previous_track()
self.assertEqual(mocked_status.call_count, 3)
self.assertEqual(mocked_previous_track.call_count, 1)
@ -509,13 +573,14 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
self.assertEqual(mocked_sountouch_device.call_count, 1)
self.assertEqual(mocked_status.call_count, 1)
self.assertEqual(mocked_volume.call_count, 1)
soundtouch.DEVICES[0].play_media('PLAYLIST', 1)
all_devices[0].play_media('PLAYLIST', 1)
self.assertEqual(mocked_presets.call_count, 1)
self.assertEqual(mocked_select_preset.call_count, 1)
soundtouch.DEVICES[0].play_media('PLAYLIST', 2)
all_devices[0].play_media('PLAYLIST', 2)
self.assertEqual(mocked_presets.call_count, 2)
self.assertEqual(mocked_select_preset.call_count, 1)
@ -533,26 +598,30 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].entity_id = "entity_1"
soundtouch.DEVICES[1].entity_id = "entity_2"
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].entity_id = "media_player.entity_1"
all_devices[1].entity_id = "media_player.entity_2"
self.assertEqual(mocked_sountouch_device.call_count, 2)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 2)
# one master, one slave => create zone
service = MockService("entity_1", [])
soundtouch.play_everywhere_service(service)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_PLAY_EVERYWHERE,
{"master": "media_player.entity_1"}, True)
self.assertEqual(mocked_create_zone.call_count, 1)
# unknown master. create zone is must not be called
service = MockService("entity_X", [])
soundtouch.play_everywhere_service(service)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_PLAY_EVERYWHERE,
{"master": "media_player.entity_X"}, True)
self.assertEqual(mocked_create_zone.call_count, 1)
# no slaves, create zone must not be called
soundtouch.DEVICES.pop(1)
service = MockService("entity_1", [])
soundtouch.play_everywhere_service(service)
all_devices.pop(1)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_PLAY_EVERYWHERE,
{"master": "media_player.entity_1"}, True)
self.assertEqual(mocked_create_zone.call_count, 1)
@mock.patch('libsoundtouch.device.SoundTouchDevice.create_zone')
@ -569,63 +638,34 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].entity_id = "entity_1"
soundtouch.DEVICES[1].entity_id = "entity_2"
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].entity_id = "media_player.entity_1"
all_devices[1].entity_id = "media_player.entity_2"
self.assertEqual(mocked_sountouch_device.call_count, 2)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 2)
# one master, one slave => create zone
service = MockService("entity_1", ["entity_2"])
soundtouch.create_zone_service(service)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_CREATE_ZONE,
{"master": "media_player.entity_1",
"slaves": ["media_player.entity_2"]}, True)
self.assertEqual(mocked_create_zone.call_count, 1)
# unknown master. create zone is must not be called
service = MockService("entity_X", [])
soundtouch.create_zone_service(service)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_CREATE_ZONE,
{"master": "media_player.entity_X",
"slaves": ["media_player.entity_2"]}, True)
self.assertEqual(mocked_create_zone.call_count, 1)
# no slaves, create zone must not be called
soundtouch.DEVICES.pop(1)
service = MockService("entity_1", [])
soundtouch.create_zone_service(service)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_CREATE_ZONE,
{"master": "media_player.entity_X",
"slaves": []}, True)
self.assertEqual(mocked_create_zone.call_count, 1)
@mock.patch('libsoundtouch.device.SoundTouchDevice.add_zone_slave')
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status')
@mock.patch('libsoundtouch.soundtouch_device',
side_effect=_mock_soundtouch_device)
def test_add_zone_slave(self, mocked_sountouch_device, mocked_status,
mocked_volume, mocked_add_zone_slave):
"""Test adding a slave to an existing zone."""
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].entity_id = "entity_1"
soundtouch.DEVICES[1].entity_id = "entity_2"
self.assertEqual(mocked_sountouch_device.call_count, 2)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 2)
# remove one slave
service = MockService("entity_1", ["entity_2"])
soundtouch.add_zone_slave(service)
self.assertEqual(mocked_add_zone_slave.call_count, 1)
# unknown master. add zone slave is not called
service = MockService("entity_X", ["entity_2"])
soundtouch.add_zone_slave(service)
self.assertEqual(mocked_add_zone_slave.call_count, 1)
# no slave to add, add zone slave is not called
service = MockService("entity_1", [])
soundtouch.add_zone_slave(service)
self.assertEqual(mocked_add_zone_slave.call_count, 1)
@mock.patch('libsoundtouch.device.SoundTouchDevice.remove_zone_slave')
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status')
@ -633,6 +673,48 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
side_effect=_mock_soundtouch_device)
def test_remove_zone_slave(self, mocked_sountouch_device, mocked_status,
mocked_volume, mocked_remove_zone_slave):
"""Test adding a slave to an existing zone."""
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].entity_id = "media_player.entity_1"
all_devices[1].entity_id = "media_player.entity_2"
self.assertEqual(mocked_sountouch_device.call_count, 2)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 2)
# remove one slave
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_REMOVE_ZONE_SLAVE,
{"master": "media_player.entity_1",
"slaves": ["media_player.entity_2"]}, True)
self.assertEqual(mocked_remove_zone_slave.call_count, 1)
# unknown master. add zone slave is not called
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_REMOVE_ZONE_SLAVE,
{"master": "media_player.entity_X",
"slaves": ["media_player.entity_2"]}, True)
self.assertEqual(mocked_remove_zone_slave.call_count, 1)
# no slave to add, add zone slave is not called
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_REMOVE_ZONE_SLAVE,
{"master": "media_player.entity_1",
"slaves": []}, True)
self.assertEqual(mocked_remove_zone_slave.call_count, 1)
@mock.patch('libsoundtouch.device.SoundTouchDevice.add_zone_slave')
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
@mock.patch('libsoundtouch.device.SoundTouchDevice.status')
@mock.patch('libsoundtouch.soundtouch_device',
side_effect=_mock_soundtouch_device)
def test_add_zone_slave(self, mocked_sountouch_device, mocked_status,
mocked_volume, mocked_add_zone_slave):
"""Test removing a slave from a zone."""
soundtouch.setup_platform(self.hass,
default_component(),
@ -640,23 +722,30 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
soundtouch.setup_platform(self.hass,
default_component(),
mock.MagicMock())
soundtouch.DEVICES[0].entity_id = "entity_1"
soundtouch.DEVICES[1].entity_id = "entity_2"
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
all_devices[0].entity_id = "media_player.entity_1"
all_devices[1].entity_id = "media_player.entity_2"
self.assertEqual(mocked_sountouch_device.call_count, 2)
self.assertEqual(mocked_status.call_count, 2)
self.assertEqual(mocked_volume.call_count, 2)
# remove one slave
service = MockService("entity_1", ["entity_2"])
soundtouch.remove_zone_slave(service)
self.assertEqual(mocked_remove_zone_slave.call_count, 1)
# add one slave
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_ADD_ZONE_SLAVE,
{"master": "media_player.entity_1",
"slaves": ["media_player.entity_2"]}, True)
self.assertEqual(mocked_add_zone_slave.call_count, 1)
# unknown master. remove zone slave is not called
service = MockService("entity_X", ["entity_2"])
soundtouch.remove_zone_slave(service)
self.assertEqual(mocked_remove_zone_slave.call_count, 1)
# unknown master. add zone slave is not called
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_ADD_ZONE_SLAVE,
{"master": "media_player.entity_X",
"slaves": ["media_player.entity_2"]}, True)
self.assertEqual(mocked_add_zone_slave.call_count, 1)
# no slave to add, add zone slave is not called
service = MockService("entity_1", [])
soundtouch.remove_zone_slave(service)
self.assertEqual(mocked_remove_zone_slave.call_count, 1)
self.hass.services.call(soundtouch.DOMAIN,
soundtouch.SERVICE_ADD_ZONE_SLAVE,
{"master": "media_player.entity_1",
"slaves": ["media_player.entity_X"]}, True)
self.assertEqual(mocked_add_zone_slave.call_count, 1)