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__)
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(
{

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 .coordinator import SleepIQData
from .entity import SleepIQSensor
from .entity import SleepIQSleeperEntity
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."""
_attr_device_class = BinarySensorDeviceClass.OCCUPANCY

View File

@ -34,7 +34,10 @@ class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[None]):
self.client = client
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]):

View File

@ -33,7 +33,7 @@ class SleepIQEntity(Entity):
self._attr_device_info = device_from_bed(bed)
class SleepIQSensor(CoordinatorEntity):
class SleepIQBedEntity(CoordinatorEntity):
"""Implementation of a SleepIQ sensor."""
_attr_icon = ICON_OCCUPIED
@ -42,17 +42,11 @@ class SleepIQSensor(CoordinatorEntity):
self,
coordinator: DataUpdateCoordinator,
bed: SleepIQBed,
sleeper: SleepIQSleeper,
name: str,
) -> None:
"""Initialize the SleepIQ sensor entity."""
super().__init__(coordinator)
self.sleeper = sleeper
self.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()
@callback
@ -67,7 +61,7 @@ class SleepIQSensor(CoordinatorEntity):
"""Update sensor attributes."""
class SleepIQBedCoordinator(CoordinatorEntity):
class SleepIQSleeperEntity(SleepIQBedEntity):
"""Implementation of a SleepIQ sensor."""
_attr_icon = ICON_OCCUPIED
@ -76,8 +70,12 @@ class SleepIQBedCoordinator(CoordinatorEntity):
self,
coordinator: DataUpdateCoordinator,
bed: SleepIQBed,
sleeper: SleepIQSleeper,
name: str,
) -> None:
"""Initialize the SleepIQ sensor entity."""
super().__init__(coordinator)
self.bed = bed
self._attr_device_info = device_from_bed(bed)
self.sleeper = sleeper
super().__init__(coordinator, 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 .coordinator import SleepIQData
from .entity import SleepIQSensor
from .entity import SleepIQSleeperEntity
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."""
_attr_icon = "mdi:bed"

View File

@ -7,12 +7,12 @@ from asyncsleepiq import SleepIQBed
from homeassistant.components.switch import SwitchEntity
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 .const import DOMAIN
from .coordinator import SleepIQData, SleepIQPauseUpdateCoordinator
from .entity import SleepIQBedCoordinator
from .entity import SleepIQBedEntity
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."""
def __init__(
@ -39,15 +39,17 @@ class SleepNumberPrivateSwitch(SleepIQBedCoordinator, SwitchEntity):
self._attr_name = f"SleepNumber {bed.name} 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:
"""Turn on switch."""
await self.bed.set_pause_mode(True)
self._handle_coordinator_update()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off switch."""
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 asyncsleepiq import SleepIQBed, SleepIQSleeper
from asyncsleepiq import SleepIQBed, SleepIQFoundation, SleepIQLight, SleepIQSleeper
import pytest
from homeassistant.components.sleepiq import DOMAIN
@ -54,6 +54,15 @@ def mock_asyncsleepiq():
sleeper_r.in_bed = False
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

View File

@ -118,6 +118,9 @@ async def test_reauth_password(hass):
with patch(
"homeassistant.components.sleepiq.config_flow.AsyncSleepIQ.login",
return_value=True,
), patch(
"homeassistant.components.sleepiq.async_setup_entry",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
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
)