Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions homeassistant/components/media_player/sonos.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
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'

Expand All @@ -62,6 +63,11 @@

# 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'

Expand Down Expand Up @@ -90,6 +96,14 @@
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."""
Expand Down Expand Up @@ -166,6 +180,8 @@ def service_handle(service):
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)

Expand Down Expand Up @@ -193,6 +209,11 @@ def service_handle(service):
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."""
Expand Down Expand Up @@ -1034,6 +1055,30 @@ def clear_sleep_timer(self):
"""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."""
Expand Down
32 changes: 32 additions & 0 deletions tests/components/media_player/test_sonos.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -307,6 +309,36 @@ def test_sonos_clear_sleep_timer(self, set_sleep_timerMock, *args):
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')
Expand Down