Add light entity to SleepIQ (#67363)

This commit is contained in:
Keilin Bickar 2022-03-03 14:42:33 -05:00 committed by GitHub
parent cfed1ff799
commit 423c14e2a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 26 deletions

View File

@ -26,7 +26,13 @@ from .coordinator import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SWITCH] PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
]
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {

View File

@ -12,7 +12,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED from .const import DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED
from .coordinator import SleepIQData from .coordinator import SleepIQData
from .entity import SleepIQSensor from .entity import SleepIQSleeperEntity
async def async_setup_entry( async def async_setup_entry(
@ -29,7 +29,7 @@ async def async_setup_entry(
) )
class IsInBedBinarySensor(SleepIQSensor, BinarySensorEntity): class IsInBedBinarySensor(SleepIQSleeperEntity, BinarySensorEntity):
"""Implementation of a SleepIQ presence sensor.""" """Implementation of a SleepIQ presence sensor."""
_attr_device_class = BinarySensorDeviceClass.OCCUPANCY _attr_device_class = BinarySensorDeviceClass.OCCUPANCY

View File

@ -34,7 +34,10 @@ class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[None]):
self.client = client self.client = client
async def _async_update_data(self) -> None: async def _async_update_data(self) -> None:
await self.client.fetch_bed_statuses() tasks = [self.client.fetch_bed_statuses()] + [
bed.foundation.update_lights() for bed in self.client.beds.values()
]
await asyncio.gather(*tasks)
class SleepIQPauseUpdateCoordinator(DataUpdateCoordinator[None]): class SleepIQPauseUpdateCoordinator(DataUpdateCoordinator[None]):

View File

@ -33,7 +33,7 @@ class SleepIQEntity(Entity):
self._attr_device_info = device_from_bed(bed) self._attr_device_info = device_from_bed(bed)
class SleepIQSensor(CoordinatorEntity): class SleepIQBedEntity(CoordinatorEntity):
"""Implementation of a SleepIQ sensor.""" """Implementation of a SleepIQ sensor."""
_attr_icon = ICON_OCCUPIED _attr_icon = ICON_OCCUPIED
@ -42,17 +42,11 @@ class SleepIQSensor(CoordinatorEntity):
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
bed: SleepIQBed, bed: SleepIQBed,
sleeper: SleepIQSleeper,
name: str,
) -> None: ) -> None:
"""Initialize the SleepIQ sensor entity.""" """Initialize the SleepIQ sensor entity."""
super().__init__(coordinator) super().__init__(coordinator)
self.sleeper = sleeper
self.bed = bed self.bed = bed
self._attr_device_info = device_from_bed(bed) self._attr_device_info = device_from_bed(bed)
self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}"
self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}"
self._async_update_attrs() self._async_update_attrs()
@callback @callback
@ -67,7 +61,7 @@ class SleepIQSensor(CoordinatorEntity):
"""Update sensor attributes.""" """Update sensor attributes."""
class SleepIQBedCoordinator(CoordinatorEntity): class SleepIQSleeperEntity(SleepIQBedEntity):
"""Implementation of a SleepIQ sensor.""" """Implementation of a SleepIQ sensor."""
_attr_icon = ICON_OCCUPIED _attr_icon = ICON_OCCUPIED
@ -76,8 +70,12 @@ class SleepIQBedCoordinator(CoordinatorEntity):
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
bed: SleepIQBed, bed: SleepIQBed,
sleeper: SleepIQSleeper,
name: str,
) -> None: ) -> None:
"""Initialize the SleepIQ sensor entity.""" """Initialize the SleepIQ sensor entity."""
super().__init__(coordinator) self.sleeper = sleeper
self.bed = bed super().__init__(coordinator, bed)
self._attr_device_info = device_from_bed(bed)
self._attr_name = f"SleepNumber {bed.name} {sleeper.name} {SENSOR_TYPES[name]}"
self._attr_unique_id = f"{bed.id}_{sleeper.name}_{name}"

View File

@ -0,0 +1,59 @@
"""Support for SleepIQ outlet lights."""
import logging
from typing import Any
from asyncsleepiq import SleepIQBed, SleepIQLight
from homeassistant.components.light import LightEntity
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
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the SleepIQ bed lights."""
data: SleepIQData = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
SleepIQLightEntity(data.data_coordinator, bed, light)
for bed in data.client.beds.values()
for light in bed.foundation.lights
)
class SleepIQLightEntity(SleepIQBedEntity, LightEntity):
"""Representation of a light."""
def __init__(
self, coordinator: DataUpdateCoordinator, bed: SleepIQBed, light: SleepIQLight
) -> None:
"""Initialize the light."""
self.light = light
super().__init__(coordinator, bed)
self._attr_name = f"SleepNumber {bed.name} Light {light.outlet_id}"
self._attr_unique_id = f"{bed.id}-light-{light.outlet_id}"
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on light."""
await self.light.turn_on()
self._handle_coordinator_update()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off light."""
await self.light.turn_off()
self._handle_coordinator_update()
@callback
def _async_update_attrs(self) -> None:
"""Update light attributes."""
self._attr_is_on = self.light.is_on

View File

@ -11,7 +11,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, SLEEP_NUMBER from .const import DOMAIN, SLEEP_NUMBER
from .coordinator import SleepIQData from .coordinator import SleepIQData
from .entity import SleepIQSensor from .entity import SleepIQSleeperEntity
async def async_setup_entry( async def async_setup_entry(
@ -28,7 +28,7 @@ async def async_setup_entry(
) )
class SleepNumberSensorEntity(SleepIQSensor, SensorEntity): class SleepNumberSensorEntity(SleepIQSleeperEntity, SensorEntity):
"""Representation of an SleepIQ Entity with CoordinatorEntity.""" """Representation of an SleepIQ Entity with CoordinatorEntity."""
_attr_icon = "mdi:bed" _attr_icon = "mdi:bed"

View File

@ -7,12 +7,12 @@ from asyncsleepiq import SleepIQBed
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .coordinator import SleepIQData, SleepIQPauseUpdateCoordinator from .coordinator import SleepIQData, SleepIQPauseUpdateCoordinator
from .entity import SleepIQBedCoordinator from .entity import SleepIQBedEntity
async def async_setup_entry( async def async_setup_entry(
@ -28,7 +28,7 @@ async def async_setup_entry(
) )
class SleepNumberPrivateSwitch(SleepIQBedCoordinator, SwitchEntity): class SleepNumberPrivateSwitch(SleepIQBedEntity, SwitchEntity):
"""Representation of SleepIQ privacy mode.""" """Representation of SleepIQ privacy mode."""
def __init__( def __init__(
@ -39,15 +39,17 @@ class SleepNumberPrivateSwitch(SleepIQBedCoordinator, SwitchEntity):
self._attr_name = f"SleepNumber {bed.name} Pause Mode" self._attr_name = f"SleepNumber {bed.name} Pause Mode"
self._attr_unique_id = f"{bed.id}-pause-mode" self._attr_unique_id = f"{bed.id}-pause-mode"
@property
def is_on(self) -> bool:
"""Return whether the switch is on or off."""
return bool(self.bed.paused)
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on switch.""" """Turn on switch."""
await self.bed.set_pause_mode(True) await self.bed.set_pause_mode(True)
self._handle_coordinator_update()
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off switch.""" """Turn off switch."""
await self.bed.set_pause_mode(False) await self.bed.set_pause_mode(False)
self._handle_coordinator_update()
@callback
def _async_update_attrs(self) -> None:
"""Update switch attributes."""
self._attr_is_on = self.bed.paused

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from unittest.mock import create_autospec, patch from unittest.mock import create_autospec, patch
from asyncsleepiq import SleepIQBed, SleepIQSleeper from asyncsleepiq import SleepIQBed, SleepIQFoundation, SleepIQLight, SleepIQSleeper
import pytest import pytest
from homeassistant.components.sleepiq import DOMAIN from homeassistant.components.sleepiq import DOMAIN
@ -54,6 +54,15 @@ def mock_asyncsleepiq():
sleeper_r.in_bed = False sleeper_r.in_bed = False
sleeper_r.sleep_number = 80 sleeper_r.sleep_number = 80
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]
yield client yield client

View File

@ -118,6 +118,9 @@ async def test_reauth_password(hass):
with patch( with patch(
"homeassistant.components.sleepiq.config_flow.AsyncSleepIQ.login", "homeassistant.components.sleepiq.config_flow.AsyncSleepIQ.login",
return_value=True, return_value=True,
), patch(
"homeassistant.components.sleepiq.async_setup_entry",
return_value=True,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],

View File

@ -0,0 +1,73 @@
"""The tests for SleepIQ light platform."""
from homeassistant.components.light import DOMAIN
from homeassistant.components.sleepiq.coordinator import LONGER_UPDATE_INTERVAL
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.helpers import entity_registry as er
from homeassistant.util.dt import utcnow
from tests.common import async_fire_time_changed
from tests.components.sleepiq.conftest import (
BED_ID,
BED_NAME,
BED_NAME_LOWER,
setup_platform,
)
async def test_setup(hass, mock_asyncsleepiq):
"""Test for successfully setting up the SleepIQ platform."""
entry = await setup_platform(hass, DOMAIN)
entity_registry = er.async_get(hass)
assert len(entity_registry.entities) == 2
entry = entity_registry.async_get(f"light.sleepnumber_{BED_NAME_LOWER}_light_1")
assert entry
assert entry.original_name == f"SleepNumber {BED_NAME} Light 1"
assert entry.unique_id == f"{BED_ID}-light-1"
entry = entity_registry.async_get(f"light.sleepnumber_{BED_NAME_LOWER}_light_2")
assert entry
assert entry.original_name == f"SleepNumber {BED_NAME} Light 2"
assert entry.unique_id == f"{BED_ID}-light-2"
async def test_light_set_states(hass, mock_asyncsleepiq):
"""Test light change."""
await setup_platform(hass, DOMAIN)
await hass.services.async_call(
DOMAIN,
"turn_on",
{ATTR_ENTITY_ID: f"light.sleepnumber_{BED_NAME_LOWER}_light_1"},
blocking=True,
)
await hass.async_block_till_done()
mock_asyncsleepiq.beds[BED_ID].foundation.lights[0].turn_on.assert_called_once()
await hass.services.async_call(
DOMAIN,
"turn_off",
{ATTR_ENTITY_ID: f"light.sleepnumber_{BED_NAME_LOWER}_light_1"},
blocking=True,
)
await hass.async_block_till_done()
mock_asyncsleepiq.beds[BED_ID].foundation.lights[0].turn_off.assert_called_once()
async def test_switch_get_states(hass, mock_asyncsleepiq):
"""Test light update."""
await setup_platform(hass, DOMAIN)
assert (
hass.states.get(f"light.sleepnumber_{BED_NAME_LOWER}_light_1").state
== STATE_OFF
)
mock_asyncsleepiq.beds[BED_ID].foundation.lights[0].is_on = True
async_fire_time_changed(hass, utcnow() + LONGER_UPDATE_INTERVAL)
await hass.async_block_till_done()
assert (
hass.states.get(f"light.sleepnumber_{BED_NAME_LOWER}_light_1").state == STATE_ON
)