Add SleepIQ select entity for foundation preset positions (#68489)

This commit is contained in:
Mike Fugate 2022-03-23 22:14:39 -04:00 committed by GitHub
parent 661f2fd613
commit ad7a2c298b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 292 additions and 34 deletions

View file

@ -35,6 +35,7 @@ PLATFORMS = [
Platform.BUTTON, Platform.BUTTON,
Platform.LIGHT, Platform.LIGHT,
Platform.NUMBER, Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR, Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
] ]

View file

@ -0,0 +1,82 @@
"""Support for SleepIQ foundation preset selection."""
from __future__ import annotations
from asyncsleepiq import (
FAVORITE,
FLAT,
READ,
SNORE,
WATCH_TV,
ZERO_G,
SleepIQBed,
SleepIQPreset,
)
from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
from .coordinator import SleepIQData
from .entity import SleepIQBedEntity
FOUNDATION_PRESET_NAMES = {
"Not at preset": 0,
"Favorite": FAVORITE,
"Read": READ,
"Watch TV": WATCH_TV,
"Flat": FLAT,
"Zero G": ZERO_G,
"Snore": SNORE,
}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the SleepIQ foundation preset select entities."""
data: SleepIQData = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
SleepIQSelectEntity(data.data_coordinator, bed, preset)
for bed in data.client.beds.values()
for preset in bed.foundation.presets
)
class SleepIQSelectEntity(SleepIQBedEntity, SelectEntity):
"""Representation of a SleepIQ select entity."""
_attr_options = list(FOUNDATION_PRESET_NAMES.keys())
def __init__(
self, coordinator: DataUpdateCoordinator, bed: SleepIQBed, preset: SleepIQPreset
) -> None:
"""Initialize the select entity."""
self.preset = preset
if preset.side:
self._attr_name = (
f"SleepNumber {bed.name} Foundation Preset {preset.side_full}"
)
self._attr_unique_id = f"{bed.id}_preset_{preset.side}"
else:
self._attr_name = f"SleepNumber {bed.name} Foundation Preset"
self._attr_unique_id = f"{bed.id}_preset"
super().__init__(coordinator, bed)
self._async_update_attrs()
@callback
def _async_update_attrs(self) -> None:
"""Update entity attributes."""
self._attr_current_option = self.preset.preset
async def async_select_option(self, option: str) -> None:
"""Change the current preset."""
await self.preset.set_preset(FOUNDATION_PRESET_NAMES[option])
self._attr_current_option = option
self.async_write_ha_state()

View file

@ -1,13 +1,15 @@
"""Common methods for SleepIQ.""" """Common methods for SleepIQ."""
from __future__ import annotations from __future__ import annotations
from unittest.mock import create_autospec, patch from collections.abc import Generator
from unittest.mock import MagicMock, create_autospec, patch
from asyncsleepiq import ( from asyncsleepiq import (
SleepIQActuator, SleepIQActuator,
SleepIQBed, SleepIQBed,
SleepIQFoundation, SleepIQFoundation,
SleepIQLight, SleepIQLight,
SleepIQPreset,
SleepIQSleeper, SleepIQSleeper,
) )
import pytest import pytest
@ -28,6 +30,8 @@ SLEEPER_L_NAME = "SleeperL"
SLEEPER_R_NAME = "Sleeper R" SLEEPER_R_NAME = "Sleeper R"
SLEEPER_L_NAME_LOWER = SLEEPER_L_NAME.lower().replace(" ", "_") SLEEPER_L_NAME_LOWER = SLEEPER_L_NAME.lower().replace(" ", "_")
SLEEPER_R_NAME_LOWER = SLEEPER_R_NAME.lower().replace(" ", "_") SLEEPER_R_NAME_LOWER = SLEEPER_R_NAME.lower().replace(" ", "_")
PRESET_L_STATE = "Watch TV"
PRESET_R_STATE = "Flat"
SLEEPIQ_CONFIG = { SLEEPIQ_CONFIG = {
CONF_USERNAME: "user@email.com", CONF_USERNAME: "user@email.com",
@ -36,48 +40,88 @@ SLEEPIQ_CONFIG = {
@pytest.fixture @pytest.fixture
def mock_asyncsleepiq(): def mock_bed() -> MagicMock:
"""Mock an AsyncSleepIQ object.""" """Mock a SleepIQBed object with sleepers and lights."""
bed = create_autospec(SleepIQBed)
bed.name = BED_NAME
bed.id = BED_ID
bed.mac_addr = "12:34:56:78:AB:CD"
bed.model = "C10"
bed.paused = False
sleeper_l = create_autospec(SleepIQSleeper)
sleeper_r = create_autospec(SleepIQSleeper)
bed.sleepers = [sleeper_l, sleeper_r]
sleeper_l.side = "L"
sleeper_l.name = SLEEPER_L_NAME
sleeper_l.in_bed = True
sleeper_l.sleep_number = 40
sleeper_l.pressure = 1000
sleeper_l.sleeper_id = SLEEPER_L_ID
sleeper_r.side = "R"
sleeper_r.name = SLEEPER_R_NAME
sleeper_r.in_bed = False
sleeper_r.sleep_number = 80
sleeper_r.pressure = 1400
sleeper_r.sleeper_id = SLEEPER_R_ID
bed.foundation = create_autospec(SleepIQFoundation)
light_1 = create_autospec(SleepIQLight)
light_1.outlet_id = 1
light_1.is_on = False
light_2 = create_autospec(SleepIQLight)
light_2.outlet_id = 2
light_2.is_on = False
bed.foundation.lights = [light_1, light_2]
return bed
@pytest.fixture
def mock_asyncsleepiq_single_foundation(
mock_bed: MagicMock,
) -> Generator[MagicMock, None, None]:
"""Mock an AsyncSleepIQ object with a single foundation."""
with patch("homeassistant.components.sleepiq.AsyncSleepIQ", autospec=True) as mock: with patch("homeassistant.components.sleepiq.AsyncSleepIQ", autospec=True) as mock:
client = mock.return_value client = mock.return_value
bed = create_autospec(SleepIQBed) client.beds = {BED_ID: mock_bed}
client.beds = {BED_ID: bed}
bed.name = BED_NAME
bed.id = BED_ID
bed.mac_addr = "12:34:56:78:AB:CD"
bed.model = "C10"
bed.paused = False
sleeper_l = create_autospec(SleepIQSleeper)
sleeper_r = create_autospec(SleepIQSleeper)
bed.sleepers = [sleeper_l, sleeper_r]
sleeper_l.side = "L" actuator_h = create_autospec(SleepIQActuator)
sleeper_l.name = SLEEPER_L_NAME actuator_f = create_autospec(SleepIQActuator)
sleeper_l.in_bed = True mock_bed.foundation.actuators = [actuator_h, actuator_f]
sleeper_l.sleep_number = 40
sleeper_l.pressure = 1000
sleeper_l.sleeper_id = SLEEPER_L_ID
sleeper_r.side = "R" actuator_h.side = "R"
sleeper_r.name = SLEEPER_R_NAME actuator_h.side_full = "Right"
sleeper_r.in_bed = False actuator_h.actuator = "H"
sleeper_r.sleep_number = 80 actuator_h.actuator_full = "Head"
sleeper_r.pressure = 1400 actuator_h.position = 60
sleeper_r.sleeper_id = SLEEPER_R_ID
bed.foundation = create_autospec(SleepIQFoundation) actuator_f.side = None
light_1 = create_autospec(SleepIQLight) actuator_f.actuator = "F"
light_1.outlet_id = 1 actuator_f.actuator_full = "Foot"
light_1.is_on = False actuator_f.position = 10
light_2 = create_autospec(SleepIQLight)
light_2.outlet_id = 2 preset = create_autospec(SleepIQPreset)
light_2.is_on = False mock_bed.foundation.presets = [preset]
bed.foundation.lights = [light_1, light_2]
preset.preset = PRESET_R_STATE
preset.side = None
preset.side_full = None
yield client
@pytest.fixture
def mock_asyncsleepiq(mock_bed: MagicMock) -> Generator[MagicMock, None, None]:
"""Mock an AsyncSleepIQ object with a split foundation."""
with patch("homeassistant.components.sleepiq.AsyncSleepIQ", autospec=True) as mock:
client = mock.return_value
client.beds = {BED_ID: mock_bed}
actuator_h_r = create_autospec(SleepIQActuator) actuator_h_r = create_autospec(SleepIQActuator)
actuator_h_l = create_autospec(SleepIQActuator) actuator_h_l = create_autospec(SleepIQActuator)
actuator_f = create_autospec(SleepIQActuator) actuator_f = create_autospec(SleepIQActuator)
bed.foundation.actuators = [actuator_h_r, actuator_h_l, actuator_f] mock_bed.foundation.actuators = [actuator_h_r, actuator_h_l, actuator_f]
actuator_h_r.side = "R" actuator_h_r.side = "R"
actuator_h_r.side_full = "Right" actuator_h_r.side_full = "Right"
@ -96,6 +140,18 @@ def mock_asyncsleepiq():
actuator_f.actuator_full = "Foot" actuator_f.actuator_full = "Foot"
actuator_f.position = 10 actuator_f.position = 10
preset_l = create_autospec(SleepIQPreset)
preset_r = create_autospec(SleepIQPreset)
mock_bed.foundation.presets = [preset_l, preset_r]
preset_l.preset = PRESET_L_STATE
preset_l.side = "L"
preset_l.side_full = "Left"
preset_r.preset = PRESET_R_STATE
preset_r.side = "R"
preset_r.side_full = "Right"
yield client yield client

View file

@ -0,0 +1,119 @@
"""Tests for the SleepIQ select platform."""
from unittest.mock import MagicMock
from asyncsleepiq import ZERO_G
from homeassistant.components.select import DOMAIN, SERVICE_SELECT_OPTION
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
ATTR_ICON,
ATTR_OPTION,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.components.sleepiq.conftest import (
BED_ID,
BED_NAME,
BED_NAME_LOWER,
PRESET_L_STATE,
PRESET_R_STATE,
setup_platform,
)
async def test_split_foundation_preset(
hass: HomeAssistant, mock_asyncsleepiq: MagicMock
) -> None:
"""Test the SleepIQ select entity for split foundation presets."""
entry = await setup_platform(hass, DOMAIN)
entity_registry = er.async_get(hass)
state = hass.states.get(
f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset_right"
)
assert state.state == PRESET_R_STATE
assert state.attributes.get(ATTR_ICON) == "mdi:bed"
assert (
state.attributes.get(ATTR_FRIENDLY_NAME)
== f"SleepNumber {BED_NAME} Foundation Preset Right"
)
entry = entity_registry.async_get(
f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset_right"
)
assert entry
assert entry.unique_id == f"{BED_ID}_preset_R"
state = hass.states.get(
f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset_left"
)
assert state.state == PRESET_L_STATE
assert state.attributes.get(ATTR_ICON) == "mdi:bed"
assert (
state.attributes.get(ATTR_FRIENDLY_NAME)
== f"SleepNumber {BED_NAME} Foundation Preset Left"
)
entry = entity_registry.async_get(
f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset_left"
)
assert entry
assert entry.unique_id == f"{BED_ID}_preset_L"
await hass.services.async_call(
DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset_left",
ATTR_OPTION: "Zero G",
},
blocking=True,
)
await hass.async_block_till_done()
mock_asyncsleepiq.beds[BED_ID].foundation.presets[0].set_preset.assert_called_once()
mock_asyncsleepiq.beds[BED_ID].foundation.presets[0].set_preset.assert_called_with(
ZERO_G
)
async def test_single_foundation_preset(
hass: HomeAssistant, mock_asyncsleepiq_single_foundation: MagicMock
) -> None:
"""Test the SleepIQ select entity for single foundation presets."""
entry = await setup_platform(hass, DOMAIN)
entity_registry = er.async_get(hass)
state = hass.states.get(f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset")
assert state.state == PRESET_R_STATE
assert state.attributes.get(ATTR_ICON) == "mdi:bed"
assert (
state.attributes.get(ATTR_FRIENDLY_NAME)
== f"SleepNumber {BED_NAME} Foundation Preset"
)
entry = entity_registry.async_get(
f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset"
)
assert entry
assert entry.unique_id == f"{BED_ID}_preset"
await hass.services.async_call(
DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: f"select.sleepnumber_{BED_NAME_LOWER}_foundation_preset",
ATTR_OPTION: "Zero G",
},
blocking=True,
)
await hass.async_block_till_done()
mock_asyncsleepiq_single_foundation.beds[BED_ID].foundation.presets[
0
].set_preset.assert_called_once()
mock_asyncsleepiq_single_foundation.beds[BED_ID].foundation.presets[
0
].set_preset.assert_called_with(ZERO_G)