diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index c27ae16b926..c209fde1679 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -51,6 +51,7 @@ SERVICE_SNAPSHOT = 'sonos_snapshot' SERVICE_RESTORE = 'sonos_restore' SERVICE_SET_TIMER = 'sonos_set_sleep_timer' SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer' +SERVICE_UPDATE_ALARM = 'sonos_update_alarm' DATA_SONOS = 'sonos' @@ -62,6 +63,11 @@ CONF_INTERFACE_ADDR = 'interface_addr' # Service call validation schemas ATTR_SLEEP_TIME = 'sleep_time' +ATTR_ALARM_ID = 'alarm_id' +ATTR_VOLUME = 'volume' +ATTR_ENABLED = 'enabled' +ATTR_INCLUDE_LINKED_ZONES = 'include_linked_zones' +ATTR_TIME = 'time' ATTR_MASTER = 'master' ATTR_WITH_GROUP = 'with_group' @@ -90,6 +96,14 @@ SONOS_SET_TIMER_SCHEMA = SONOS_SCHEMA.extend({ vol.All(vol.Coerce(int), vol.Range(min=0, max=86399)) }) +SONOS_UPDATE_ALARM_SCHEMA = SONOS_SCHEMA.extend({ + vol.Required(ATTR_ALARM_ID): cv.positive_int, + vol.Optional(ATTR_TIME): cv.time, + vol.Optional(ATTR_VOLUME): cv.small_float, + vol.Optional(ATTR_ENABLED): cv.boolean, + vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Sonos platform.""" @@ -166,6 +180,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device.set_sleep_timer(service.data[ATTR_SLEEP_TIME]) elif service.service == SERVICE_CLEAR_TIMER: device.clear_sleep_timer() + elif service.service == SERVICE_UPDATE_ALARM: + device.update_alarm(**service.data) device.schedule_update_ha_state(True) @@ -193,6 +209,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): DOMAIN, SERVICE_CLEAR_TIMER, service_handle, descriptions.get(SERVICE_CLEAR_TIMER), schema=SONOS_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_UPDATE_ALARM, service_handle, + descriptions.get(SERVICE_UPDATE_ALARM), + schema=SONOS_UPDATE_ALARM_SCHEMA) + def _parse_timespan(timespan): """Parse a time-span into number of seconds.""" @@ -1034,6 +1055,30 @@ class SonosDevice(MediaPlayerDevice): """Clear the timer on the player.""" self._player.set_sleep_timer(None) + @soco_error + @soco_coordinator + def update_alarm(self, **data): + """Set the alarm clock on the player.""" + from soco import alarms + a = None + for alarm in alarms.get_alarms(self.soco): + # pylint: disable=protected-access + if alarm._alarm_id == str(data[ATTR_ALARM_ID]): + a = alarm + if a is None: + _LOGGER.warning("did not find alarm with id %s", + data[ATTR_ALARM_ID]) + return + if ATTR_TIME in data: + a.start_time = data[ATTR_TIME] + if ATTR_VOLUME in data: + a.volume = int(data[ATTR_VOLUME] * 100) + if ATTR_ENABLED in data: + a.enabled = data[ATTR_ENABLED] + if ATTR_INCLUDE_LINKED_ZONES in data: + a.include_linked_zones = data[ATTR_INCLUDE_LINKED_ZONES] + a.save() + @property def device_state_attributes(self): """Return device specific state attributes.""" diff --git a/tests/components/media_player/test_sonos.py b/tests/components/media_player/test_sonos.py index ebf92cb4d1a..8c62c6c84e9 100644 --- a/tests/components/media_player/test_sonos.py +++ b/tests/components/media_player/test_sonos.py @@ -1,9 +1,11 @@ """The tests for the Demo Media player platform.""" +import datetime import socket import unittest import soco.snapshot from unittest import mock import soco +from soco import alarms from homeassistant.setup import setup_component from homeassistant.components.media_player import sonos, DOMAIN @@ -307,6 +309,36 @@ class TestSonosMediaPlayer(unittest.TestCase): device.set_sleep_timer(None) set_sleep_timerMock.assert_called_once_with(None) + @mock.patch('soco.SoCo', new=SoCoMock) + @mock.patch('soco.alarms.Alarm') + @mock.patch('socket.create_connection', side_effect=socket.error()) + def test_update_alarm(self, soco_mock, alarm_mock, *args): + """Ensuring soco methods called for sonos_set_sleep_timer service.""" + sonos.setup_platform(self.hass, {}, fake_add_device, { + 'host': '192.0.2.1' + }) + device = self.hass.data[sonos.DATA_SONOS][-1] + device.hass = self.hass + alarm1 = alarms.Alarm(soco_mock) + alarm1.configure_mock(_alarm_id="1", start_time=None, enabled=False, + include_linked_zones=False, volume=100) + with mock.patch('soco.alarms.get_alarms', return_value=[alarm1]): + attrs = { + 'time': datetime.time(12, 00), + 'enabled': True, + 'include_linked_zones': True, + 'volume': 0.30, + } + device.update_alarm(alarm_id=2) + alarm1.save.assert_not_called() + device.update_alarm(alarm_id=1, **attrs) + self.assertEqual(alarm1.enabled, attrs['enabled']) + self.assertEqual(alarm1.start_time, attrs['time']) + self.assertEqual(alarm1.include_linked_zones, + attrs['include_linked_zones']) + self.assertEqual(alarm1.volume, 30) + alarm1.save.assert_called_once_with() + @mock.patch('soco.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @mock.patch.object(soco.snapshot.Snapshot, 'snapshot')