diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index da3f38fe560..17370d1b463 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -30,6 +30,7 @@ PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT, + Platform.NUMBER, Platform.SENSOR, Platform.SWITCH, ] diff --git a/homeassistant/components/sleepiq/const.py b/homeassistant/components/sleepiq/const.py index c1c28a7b5a8..2aabf29ef54 100644 --- a/homeassistant/components/sleepiq/const.py +++ b/homeassistant/components/sleepiq/const.py @@ -2,18 +2,19 @@ DATA_SLEEPIQ = "data_sleepiq" DOMAIN = "sleepiq" -SLEEPYQ_INVALID_CREDENTIALS_MESSAGE = "username or password" BED = "bed" +FIRMNESS = "firmness" ICON_EMPTY = "mdi:bed-empty" ICON_OCCUPIED = "mdi:bed" IS_IN_BED = "is_in_bed" -SLEEP_NUMBER = "sleep_number" PRESSURE = "pressure" +SLEEP_NUMBER = "sleep_number" SENSOR_TYPES = { - SLEEP_NUMBER: "SleepNumber", - IS_IN_BED: "Is In Bed", + FIRMNESS: "Firmness", PRESSURE: "Pressure", + IS_IN_BED: "Is In Bed", + SLEEP_NUMBER: "SleepNumber", } LEFT = "left" diff --git a/homeassistant/components/sleepiq/number.py b/homeassistant/components/sleepiq/number.py new file mode 100644 index 00000000000..39140b51a5b --- /dev/null +++ b/homeassistant/components/sleepiq/number.py @@ -0,0 +1,55 @@ +"""Support for SleepIQ SleepNumber firmness number entities.""" +from asyncsleepiq import SleepIQBed, SleepIQSleeper + +from homeassistant.components.number import NumberEntity +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, FIRMNESS +from .coordinator import SleepIQData +from .entity import SleepIQSleeperEntity + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the SleepIQ bed sensors.""" + data: SleepIQData = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + SleepNumberFirmnessEntity(data.data_coordinator, bed, sleeper) + for bed in data.client.beds.values() + for sleeper in bed.sleepers + ) + + +class SleepNumberFirmnessEntity(SleepIQSleeperEntity, NumberEntity): + """Representation of an SleepIQ Entity with CoordinatorEntity.""" + + _attr_icon = "mdi:bed" + _attr_max_value: float = 100 + _attr_min_value: float = 5 + _attr_step: float = 5 + + def __init__( + self, + coordinator: DataUpdateCoordinator, + bed: SleepIQBed, + sleeper: SleepIQSleeper, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, bed, sleeper, FIRMNESS) + + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + self._attr_value = float(self.sleeper.sleep_number) + + async def async_set_value(self, value: float) -> None: + """Set the firmness value.""" + await self.sleeper.set_sleepnumber(int(value)) + self._attr_value = value + self.async_write_ha_state() diff --git a/tests/components/sleepiq/test_number.py b/tests/components/sleepiq/test_number.py new file mode 100644 index 00000000000..f00bef2b4cb --- /dev/null +++ b/tests/components/sleepiq/test_number.py @@ -0,0 +1,68 @@ +"""The tests for SleepIQ number platform.""" +from homeassistant.components.number import DOMAIN +from homeassistant.components.number.const import ATTR_VALUE, SERVICE_SET_VALUE +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.helpers import entity_registry as er + +from tests.components.sleepiq.conftest import ( + BED_ID, + BED_NAME, + BED_NAME_LOWER, + SLEEPER_L_NAME, + SLEEPER_L_NAME_LOWER, + SLEEPER_R_NAME, + SLEEPER_R_NAME_LOWER, + setup_platform, +) + + +async def test_firmness(hass, mock_asyncsleepiq): + """Test the SleepIQ firmness number values for a bed with two sides.""" + entry = await setup_platform(hass, DOMAIN) + entity_registry = er.async_get(hass) + + state = hass.states.get( + f"number.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_firmness" + ) + assert state.state == "40.0" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_L_NAME} Firmness" + ) + + entry = entity_registry.async_get( + f"number.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_firmness" + ) + assert entry + assert entry.unique_id == f"{BED_ID}_{SLEEPER_L_NAME}_firmness" + + state = hass.states.get( + f"number.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_R_NAME_LOWER}_firmness" + ) + assert state.state == "80.0" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_R_NAME} Firmness" + ) + + entry = entity_registry.async_get( + f"number.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_R_NAME_LOWER}_firmness" + ) + assert entry + assert entry.unique_id == f"{BED_ID}_{SLEEPER_R_NAME}_firmness" + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: f"number.sleepnumber_{BED_NAME_LOWER}_{SLEEPER_L_NAME_LOWER}_firmness", + ATTR_VALUE: 42, + }, + blocking=True, + ) + await hass.async_block_till_done() + + mock_asyncsleepiq.beds[BED_ID].sleepers[0].set_sleepnumber.assert_called_once() + mock_asyncsleepiq.beds[BED_ID].sleepers[0].set_sleepnumber.assert_called_with(42)