Add sonos alarm clock update service (#7521)

* Add sonos alarm clock update service

* Add tests and break lines

* Fix style errors

* Make test work with python<3.6

* Fix last two pylint errors

* fix new line to long errors
This commit is contained in:
Marc Egli 2017-05-15 09:42:45 +02:00 committed by Paulus Schoutsen
parent 5c4a21efac
commit 4da91d6a8b
2 changed files with 77 additions and 0 deletions

View File

@ -51,6 +51,7 @@ SERVICE_SNAPSHOT = 'sonos_snapshot'
SERVICE_RESTORE = 'sonos_restore' SERVICE_RESTORE = 'sonos_restore'
SERVICE_SET_TIMER = 'sonos_set_sleep_timer' SERVICE_SET_TIMER = 'sonos_set_sleep_timer'
SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer' SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer'
SERVICE_UPDATE_ALARM = 'sonos_update_alarm'
DATA_SONOS = 'sonos' DATA_SONOS = 'sonos'
@ -62,6 +63,11 @@ 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_VOLUME = 'volume'
ATTR_ENABLED = 'enabled'
ATTR_INCLUDE_LINKED_ZONES = 'include_linked_zones'
ATTR_TIME = 'time'
ATTR_MASTER = 'master' ATTR_MASTER = 'master'
ATTR_WITH_GROUP = 'with_group' 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)) 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): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Sonos platform.""" """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]) device.set_sleep_timer(service.data[ATTR_SLEEP_TIME])
elif service.service == SERVICE_CLEAR_TIMER: elif service.service == SERVICE_CLEAR_TIMER:
device.clear_sleep_timer() device.clear_sleep_timer()
elif service.service == SERVICE_UPDATE_ALARM:
device.update_alarm(**service.data)
device.schedule_update_ha_state(True) 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, DOMAIN, SERVICE_CLEAR_TIMER, service_handle,
descriptions.get(SERVICE_CLEAR_TIMER), schema=SONOS_SCHEMA) 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): def _parse_timespan(timespan):
"""Parse a time-span into number of seconds.""" """Parse a time-span into number of seconds."""
@ -1034,6 +1055,30 @@ class SonosDevice(MediaPlayerDevice):
"""Clear the timer on the player.""" """Clear the timer on the player."""
self._player.set_sleep_timer(None) 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 @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return device specific state attributes.""" """Return device specific state attributes."""

View File

@ -1,9 +1,11 @@
"""The tests for the Demo Media player platform.""" """The tests for the Demo Media player platform."""
import datetime
import socket import socket
import unittest import unittest
import soco.snapshot import soco.snapshot
from unittest import mock from unittest import mock
import soco import soco
from soco import alarms
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
from homeassistant.components.media_player import sonos, DOMAIN from homeassistant.components.media_player import sonos, DOMAIN
@ -307,6 +309,36 @@ class TestSonosMediaPlayer(unittest.TestCase):
device.set_sleep_timer(None) device.set_sleep_timer(None)
set_sleep_timerMock.assert_called_once_with(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('soco.SoCo', new=SoCoMock)
@mock.patch('socket.create_connection', side_effect=socket.error()) @mock.patch('socket.create_connection', side_effect=socket.error())
@mock.patch.object(soco.snapshot.Snapshot, 'snapshot') @mock.patch.object(soco.snapshot.Snapshot, 'snapshot')