mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Add Sonos alarm to sonos component (#50719)
* add sonos_alarm * bug fix for _update_device * fix pylint and black and co * small bug fix in speaker.available_alarms * cleanup and add _LOGGER.debug statements, fix pylint * fix pylint * _alarm_id to alarm_id * fixed rare bug due to raceconditions * Part 2 of raceconditionfix * address review suggestions * readd check for not yet subscribed * - platforms_ready fix - add alarmClock to pytest mock * fixture for ListAlarms * cleanup mock and match UUID for test * add simple tests for sonos_alarm * extend test for attributes * typhint fix * typo * use get_alarms() directly * refactor available_alarms * fix attributes * some cleanup * change logic of fetch_alarms_for_speaker and rename to update_alarms_for_speaker * update_alarms_for_speaker is now a method * Update homeassistant/components/sonos/switch.py Co-authored-by: jjlawren <jjlawren@users.noreply.github.com> * Update homeassistant/components/sonos/speaker.py Co-authored-by: jjlawren <jjlawren@users.noreply.github.com> Co-authored-by: jjlawren <jjlawren@users.noreply.github.com>
This commit is contained in:
parent
1de4971d54
commit
9bf6ea60db
@ -9,6 +9,7 @@ import socket
|
||||
|
||||
import pysonos
|
||||
from pysonos import events_asyncio
|
||||
from pysonos.alarms import Alarm
|
||||
from pysonos.core import SoCo
|
||||
from pysonos.exceptions import SoCoException
|
||||
import voluptuous as vol
|
||||
@ -30,6 +31,7 @@ from .const import (
|
||||
DISCOVERY_INTERVAL,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
SONOS_ALARM_UPDATE,
|
||||
SONOS_GROUP_UPDATE,
|
||||
SONOS_SEEN,
|
||||
)
|
||||
@ -70,6 +72,7 @@ class SonosData:
|
||||
# OrderedDict behavior used by SonosFavorites
|
||||
self.discovered: OrderedDict[str, SonosSpeaker] = OrderedDict()
|
||||
self.favorites: dict[str, SonosFavorites] = {}
|
||||
self.alarms: dict[str, Alarm] = {}
|
||||
self.topology_condition = asyncio.Condition()
|
||||
self.discovery_thread = None
|
||||
self.hosts_heartbeat = None
|
||||
@ -174,6 +177,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
def _async_signal_update_groups(event):
|
||||
async_dispatcher_send(hass, SONOS_GROUP_UPDATE)
|
||||
|
||||
@callback
|
||||
def _async_signal_update_alarms(event):
|
||||
async_dispatcher_send(hass, SONOS_ALARM_UPDATE)
|
||||
|
||||
async def setup_platforms_and_discovery():
|
||||
await asyncio.gather(
|
||||
*[
|
||||
@ -189,6 +196,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
EVENT_HOMEASSISTANT_START, _async_signal_update_groups
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, _async_signal_update_alarms
|
||||
)
|
||||
)
|
||||
_LOGGER.debug("Adding discovery job")
|
||||
await hass.async_add_executor_job(_discovery)
|
||||
|
||||
|
@ -20,10 +20,11 @@ from homeassistant.components.media_player.const import (
|
||||
MEDIA_TYPE_TRACK,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
|
||||
DOMAIN = "sonos"
|
||||
DATA_SONOS = "sonos_media_player"
|
||||
PLATFORMS = {BINARY_SENSOR_DOMAIN, MP_DOMAIN, SENSOR_DOMAIN}
|
||||
PLATFORMS = {BINARY_SENSOR_DOMAIN, MP_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN}
|
||||
|
||||
SONOS_ARTIST = "artists"
|
||||
SONOS_ALBUM = "albums"
|
||||
@ -131,12 +132,14 @@ PLAYABLE_MEDIA_TYPES = [
|
||||
MEDIA_TYPE_TRACK,
|
||||
]
|
||||
|
||||
SONOS_CREATE_ALARM = "sonos_create_alarm"
|
||||
SONOS_CREATE_BATTERY = "sonos_create_battery"
|
||||
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
|
||||
SONOS_ENTITY_CREATED = "sonos_entity_created"
|
||||
SONOS_ENTITY_UPDATE = "sonos_entity_update"
|
||||
SONOS_GROUP_UPDATE = "sonos_group_update"
|
||||
SONOS_HOUSEHOLD_UPDATED = "sonos_household_updated"
|
||||
SONOS_ALARM_UPDATE = "sonos_alarm_update"
|
||||
SONOS_STATE_UPDATED = "sonos_state_updated"
|
||||
SONOS_SEEN = "sonos_seen"
|
||||
|
||||
|
@ -588,8 +588,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
||||
"""Set the alarm clock on the player."""
|
||||
alarm = None
|
||||
for one_alarm in alarms.get_alarms(self.coordinator.soco):
|
||||
# pylint: disable=protected-access
|
||||
if one_alarm._alarm_id == str(alarm_id):
|
||||
if one_alarm.alarm_id == str(alarm_id):
|
||||
alarm = one_alarm
|
||||
if alarm is None:
|
||||
_LOGGER.warning("Did not find alarm with id %s", alarm_id)
|
||||
|
@ -11,6 +11,7 @@ from typing import Any, Callable
|
||||
import urllib.parse
|
||||
|
||||
import async_timeout
|
||||
from pysonos.alarms import get_alarms
|
||||
from pysonos.core import MUSIC_SRC_LINE_IN, MUSIC_SRC_RADIO, MUSIC_SRC_TV, SoCo
|
||||
from pysonos.data_structures import DidlAudioBroadcast
|
||||
from pysonos.events_base import Event as SonosEvent, SubscriptionBase
|
||||
@ -21,6 +22,7 @@ from pysonos.snapshot import Snapshot
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as ent_reg
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
@ -37,6 +39,8 @@ from .const import (
|
||||
PLATFORMS,
|
||||
SCAN_INTERVAL,
|
||||
SEEN_EXPIRE_TIME,
|
||||
SONOS_ALARM_UPDATE,
|
||||
SONOS_CREATE_ALARM,
|
||||
SONOS_CREATE_BATTERY,
|
||||
SONOS_CREATE_MEDIA_PLAYER,
|
||||
SONOS_ENTITY_CREATED,
|
||||
@ -193,12 +197,17 @@ class SonosSpeaker:
|
||||
else:
|
||||
self._platforms_ready.update({BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN})
|
||||
|
||||
if new_alarms := self.update_alarms_for_speaker():
|
||||
dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms)
|
||||
else:
|
||||
self._platforms_ready.add(SWITCH_DOMAIN)
|
||||
|
||||
dispatcher_send(self.hass, SONOS_CREATE_MEDIA_PLAYER, self)
|
||||
|
||||
async def async_handle_new_entity(self, entity_type: str) -> None:
|
||||
"""Listen to new entities to trigger first subscription."""
|
||||
self._platforms_ready.add(entity_type)
|
||||
if self._platforms_ready == PLATFORMS:
|
||||
if self._platforms_ready == PLATFORMS and not self._subscriptions:
|
||||
self._resubscription_lock = asyncio.Lock()
|
||||
await self.async_subscribe()
|
||||
self._is_ready = True
|
||||
@ -244,6 +253,7 @@ class SonosSpeaker:
|
||||
self._subscribe(
|
||||
self.soco.deviceProperties, self.async_dispatch_properties
|
||||
),
|
||||
self._subscribe(self.soco.alarmClock, self.async_dispatch_alarms),
|
||||
)
|
||||
return True
|
||||
except SoCoException as ex:
|
||||
@ -266,6 +276,11 @@ class SonosSpeaker:
|
||||
"""Update properties from event."""
|
||||
self.hass.async_create_task(self.async_update_device_properties(event))
|
||||
|
||||
@callback
|
||||
def async_dispatch_alarms(self, event: SonosEvent | None = None) -> None:
|
||||
"""Update alarms from event."""
|
||||
self.hass.async_create_task(self.async_update_alarms(event))
|
||||
|
||||
@callback
|
||||
def async_dispatch_groups(self, event: SonosEvent | None = None) -> None:
|
||||
"""Update groups from event."""
|
||||
@ -365,6 +380,42 @@ class SonosSpeaker:
|
||||
|
||||
self.async_write_entity_states()
|
||||
|
||||
def update_alarms_for_speaker(self) -> set[str]:
|
||||
"""Update current alarm instances.
|
||||
|
||||
Updates hass.data[DATA_SONOS].alarms and returns a list of all alarms that are new.
|
||||
"""
|
||||
new_alarms = set()
|
||||
stored_alarms = self.hass.data[DATA_SONOS].alarms
|
||||
updated_alarms = get_alarms(self.soco)
|
||||
|
||||
for alarm in updated_alarms:
|
||||
if alarm.zone.uid == self.soco.uid and alarm.alarm_id not in list(
|
||||
stored_alarms.keys()
|
||||
):
|
||||
new_alarms.add(alarm.alarm_id)
|
||||
stored_alarms[alarm.alarm_id] = alarm
|
||||
|
||||
for alarm_id, alarm in list(stored_alarms.items()):
|
||||
if alarm not in updated_alarms:
|
||||
stored_alarms.pop(alarm_id)
|
||||
|
||||
return new_alarms
|
||||
|
||||
async def async_update_alarms(self, event: SonosEvent | None = None) -> None:
|
||||
"""Update device properties using the provided SonosEvent."""
|
||||
if event is None:
|
||||
return
|
||||
|
||||
if new_alarms := await self.hass.async_add_executor_job(
|
||||
self.update_alarms_for_speaker
|
||||
):
|
||||
async_dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms)
|
||||
|
||||
async_dispatcher_send(self.hass, SONOS_ALARM_UPDATE, self)
|
||||
|
||||
self.async_write_entity_states()
|
||||
|
||||
async def async_update_battery_info(self, battery_dict: dict[str, Any]) -> None:
|
||||
"""Update battery info using the decoded SonosEvent."""
|
||||
self._last_battery_event = dt_util.utcnow()
|
||||
|
202
homeassistant/components/sonos/switch.py
Normal file
202
homeassistant/components/sonos/switch.py
Normal file
@ -0,0 +1,202 @@
|
||||
"""Entity representing a Sonos Alarm."""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from pysonos.exceptions import SoCoUPnPException
|
||||
|
||||
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity
|
||||
from homeassistant.const import ATTR_TIME
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
DATA_SONOS,
|
||||
DOMAIN as SONOS_DOMAIN,
|
||||
SONOS_ALARM_UPDATE,
|
||||
SONOS_CREATE_ALARM,
|
||||
)
|
||||
from .entity import SonosEntity
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_DURATION = "duration"
|
||||
ATTR_ID = "alarm_id"
|
||||
ATTR_PLAY_MODE = "play_mode"
|
||||
ATTR_RECURRENCE = "recurrence"
|
||||
ATTR_SCHEDULED_TODAY = "scheduled_today"
|
||||
ATTR_VOLUME = "volume"
|
||||
ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Sonos from a config entry."""
|
||||
|
||||
configured_alarms = set()
|
||||
|
||||
async def _async_create_entity(speaker: SonosSpeaker, new_alarms: set) -> None:
|
||||
for alarm_id in new_alarms:
|
||||
if alarm_id not in configured_alarms:
|
||||
_LOGGER.debug("Creating alarm with id %s", alarm_id)
|
||||
entity = SonosAlarmEntity(alarm_id, speaker)
|
||||
async_add_entities([entity])
|
||||
configured_alarms.add(alarm_id)
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, SONOS_ALARM_UPDATE, entity.async_update
|
||||
)
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(hass, SONOS_CREATE_ALARM, _async_create_entity)
|
||||
)
|
||||
|
||||
|
||||
class SonosAlarmEntity(SonosEntity, SwitchEntity):
|
||||
"""Representation of a Sonos Alarm entity."""
|
||||
|
||||
def __init__(self, alarm_id: str, speaker: SonosSpeaker) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(speaker)
|
||||
|
||||
self._alarm_id = alarm_id
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(f"sonos_alarm_{self.alarm_id}")
|
||||
|
||||
@property
|
||||
def alarm(self):
|
||||
"""Return the ID of the alarm."""
|
||||
return self.hass.data[DATA_SONOS].alarms[self.alarm_id]
|
||||
|
||||
@property
|
||||
def alarm_id(self):
|
||||
"""Return the ID of the alarm."""
|
||||
return self._alarm_id
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID of the switch."""
|
||||
return f"{SONOS_DOMAIN}-{self.alarm_id}"
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon of Sonos alarm switch."""
|
||||
return "mdi:alarm"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return "Sonos Alarm {} {} {}".format(
|
||||
self.speaker.zone_name,
|
||||
self.alarm.recurrence.title(),
|
||||
str(self.alarm.start_time)[0:5],
|
||||
)
|
||||
|
||||
async def async_check_if_available(self):
|
||||
"""Check if alarm exists and remove alarm entity if not available."""
|
||||
if self.alarm_id in self.hass.data[DATA_SONOS].alarms:
|
||||
return True
|
||||
|
||||
_LOGGER.debug("The alarm is removed from hass because it has been deleted")
|
||||
|
||||
entity_registry = er.async_get(self.hass)
|
||||
if entity_registry.async_get(self.entity_id):
|
||||
entity_registry.async_remove(self.entity_id)
|
||||
|
||||
return False
|
||||
|
||||
async def async_update(self, now: datetime.datetime | None = None) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
if await self.async_check_if_available():
|
||||
await self.hass.async_add_executor_job(self.update_alarm)
|
||||
|
||||
def update_alarm(self):
|
||||
"""Update the state of the alarm."""
|
||||
_LOGGER.debug("Updating the state of the alarm")
|
||||
if self.speaker.soco.uid != self.alarm.zone.uid:
|
||||
self.speaker = self.hass.data[DATA_SONOS].discovered.get(
|
||||
self.alarm.zone.uid
|
||||
)
|
||||
if self.speaker is None:
|
||||
raise RuntimeError(
|
||||
"No configured Sonos speaker has been found to match the alarm."
|
||||
)
|
||||
|
||||
self._update_device()
|
||||
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def _update_device(self):
|
||||
"""Update the device, since this alarm moved to a different player."""
|
||||
device_registry = dr.async_get(self.hass)
|
||||
entity_registry = er.async_get(self.hass)
|
||||
entity = entity_registry.async_get(self.entity_id)
|
||||
|
||||
if entity is None:
|
||||
raise RuntimeError("Alarm has been deleted by accident.")
|
||||
|
||||
entry_id = entity.config_entry_id
|
||||
|
||||
new_device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry_id,
|
||||
identifiers={(SONOS_DOMAIN, self.soco.uid)},
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self.speaker.mac_address)},
|
||||
)
|
||||
if not entity_registry.async_get(self.entity_id).device_id == new_device.id:
|
||||
_LOGGER.debug("The alarm is switching the sonos player")
|
||||
# pylint: disable=protected-access
|
||||
entity_registry._async_update_entity(
|
||||
self.entity_id, device_id=new_device.id
|
||||
)
|
||||
|
||||
@property
|
||||
def _is_today(self):
|
||||
recurrence = self.alarm.recurrence
|
||||
timestr = int(datetime.datetime.today().strftime("%w"))
|
||||
return (
|
||||
bool(recurrence[:2] == "ON" and str(timestr) in recurrence)
|
||||
or bool(recurrence == "DAILY")
|
||||
or bool(recurrence == "WEEKDAYS" and int(timestr) not in [0, 7])
|
||||
or bool(recurrence == "ONCE")
|
||||
or bool(recurrence == "WEEKDAYS" and int(timestr) not in [0, 7])
|
||||
or bool(recurrence == "WEEKENDS" and int(timestr) not in range(1, 7))
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return state of Sonos alarm switch."""
|
||||
return self.alarm.enabled
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return attributes of Sonos alarm switch."""
|
||||
return {
|
||||
ATTR_ID: str(self.alarm_id),
|
||||
ATTR_TIME: str(self.alarm.start_time),
|
||||
ATTR_DURATION: str(self.alarm.duration),
|
||||
ATTR_RECURRENCE: str(self.alarm.recurrence),
|
||||
ATTR_VOLUME: self.alarm.volume / 100,
|
||||
ATTR_PLAY_MODE: str(self.alarm.play_mode),
|
||||
ATTR_SCHEDULED_TODAY: self._is_today,
|
||||
ATTR_INCLUDE_LINKED_ZONES: self.alarm.include_linked_zones,
|
||||
}
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Turn alarm switch on."""
|
||||
await self.async_handle_switch_on_off(turn_on=True)
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn alarm switch off."""
|
||||
await self.async_handle_switch_on_off(turn_on=False)
|
||||
|
||||
async def async_handle_switch_on_off(self, turn_on: bool) -> None:
|
||||
"""Handle turn on/off of alarm switch."""
|
||||
try:
|
||||
_LOGGER.debug("Switching the state of the alarm")
|
||||
self.alarm.enabled = turn_on
|
||||
await self.hass.async_add_executor_job(self.alarm.save)
|
||||
except SoCoUPnPException as exc:
|
||||
_LOGGER.warning(
|
||||
"Home Assistant couldn't switch the alarm %s", exc, exc_info=True
|
||||
)
|
@ -17,7 +17,9 @@ def config_entry_fixture():
|
||||
|
||||
|
||||
@pytest.fixture(name="soco")
|
||||
def soco_fixture(music_library, speaker_info, battery_info, dummy_soco_service):
|
||||
def soco_fixture(
|
||||
music_library, speaker_info, battery_info, dummy_soco_service, alarmClock
|
||||
):
|
||||
"""Create a mock pysonos SoCo fixture."""
|
||||
with patch("pysonos.SoCo", autospec=True) as mock, patch(
|
||||
"socket.gethostbyname", return_value="192.168.42.2"
|
||||
@ -32,12 +34,13 @@ def soco_fixture(music_library, speaker_info, battery_info, dummy_soco_service):
|
||||
mock_soco.zoneGroupTopology = dummy_soco_service
|
||||
mock_soco.contentDirectory = dummy_soco_service
|
||||
mock_soco.deviceProperties = dummy_soco_service
|
||||
mock_soco.alarmClock = alarmClock
|
||||
mock_soco.mute = False
|
||||
mock_soco.night_mode = True
|
||||
mock_soco.dialog_mode = True
|
||||
mock_soco.volume = 19
|
||||
mock_soco.get_battery_info.return_value = battery_info
|
||||
|
||||
mock_soco.all_zones = [mock_soco]
|
||||
yield mock_soco
|
||||
|
||||
|
||||
@ -75,6 +78,26 @@ def music_library_fixture():
|
||||
return music_library
|
||||
|
||||
|
||||
@pytest.fixture(name="alarmClock")
|
||||
def alarmClock_fixture():
|
||||
"""Create alarmClock fixture."""
|
||||
alarmClock = Mock()
|
||||
alarmClock.subscribe = AsyncMock()
|
||||
alarmClock.ListAlarms.return_value = {
|
||||
"CurrentAlarmList": "<Alarms>"
|
||||
'<Alarm ID="14" StartTime="07:00:00" Duration="02:00:00" Recurrence="DAILY" '
|
||||
'Enabled="1" RoomUUID="RINCON_test" ProgramURI="x-rincon-buzzer:0" '
|
||||
'ProgramMetaData="" PlayMode="SHUFFLE_NOREPEAT" Volume="25" '
|
||||
'IncludeLinkedZones="0"/>'
|
||||
'<Alarm ID="15" StartTime="07:00:00" Duration="02:00:00" '
|
||||
'Recurrence="DAILY" Enabled="1" RoomUUID="RINCON_test" '
|
||||
'ProgramURI="x-rincon-buzzer:0" ProgramMetaData="" PlayMode="SHUFFLE_NOREPEAT" '
|
||||
'Volume="25" IncludeLinkedZones="0"/>'
|
||||
"</Alarms> "
|
||||
}
|
||||
return alarmClock
|
||||
|
||||
|
||||
@pytest.fixture(name="speaker_info")
|
||||
def speaker_info_fixture():
|
||||
"""Create speaker_info fixture."""
|
||||
|
47
tests/components/sonos/test_switch.py
Normal file
47
tests/components/sonos/test_switch.py
Normal file
@ -0,0 +1,47 @@
|
||||
"""Tests for the Sonos Alarm switch platform."""
|
||||
from homeassistant.components.sonos import DOMAIN
|
||||
from homeassistant.components.sonos.switch import (
|
||||
ATTR_DURATION,
|
||||
ATTR_ID,
|
||||
ATTR_INCLUDE_LINKED_ZONES,
|
||||
ATTR_PLAY_MODE,
|
||||
ATTR_RECURRENCE,
|
||||
ATTR_VOLUME,
|
||||
)
|
||||
from homeassistant.const import ATTR_TIME, STATE_ON
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
async def setup_platform(hass, config_entry, config):
|
||||
"""Set up the media player platform for testing."""
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_entity_registry(hass, config_entry, config, soco):
|
||||
"""Test sonos device with alarm registered in the device registry."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
||||
assert "media_player.zone_a" in entity_registry.entities
|
||||
assert "switch.sonos_alarm_14" in entity_registry.entities
|
||||
|
||||
|
||||
async def test_alarm_attributes(hass, config_entry, config, soco):
|
||||
"""Test for correct sonos alarm state."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
||||
alarm = entity_registry.entities["switch.sonos_alarm_14"]
|
||||
alarm_state = hass.states.get(alarm.entity_id)
|
||||
assert alarm_state.state == STATE_ON
|
||||
assert alarm_state.attributes.get(ATTR_TIME) == "07:00:00"
|
||||
assert alarm_state.attributes.get(ATTR_ID) == "14"
|
||||
assert alarm_state.attributes.get(ATTR_DURATION) == "02:00:00"
|
||||
assert alarm_state.attributes.get(ATTR_RECURRENCE) == "DAILY"
|
||||
assert alarm_state.attributes.get(ATTR_VOLUME) == 0.25
|
||||
assert alarm_state.attributes.get(ATTR_PLAY_MODE) == "SHUFFLE_NOREPEAT"
|
||||
assert not alarm_state.attributes.get(ATTR_INCLUDE_LINKED_ZONES)
|
Loading…
x
Reference in New Issue
Block a user