From 34f1d5e094745e0331463904b5fbc49761234dea Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Wed, 13 Jul 2022 09:21:58 +0200 Subject: [PATCH] Add Plugwise number platform (#74655) --- homeassistant/components/plugwise/const.py | 3 +- .../components/plugwise/manifest.json | 2 +- homeassistant/components/plugwise/number.py | 115 ++++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../fixtures/anna_heatpump/all_data.json | 3 + tests/components/plugwise/test_number.py | 40 ++++++ 7 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/plugwise/number.py create mode 100644 tests/components/plugwise/test_number.py diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 63bd2a6d8f1..d56d9c06ff5 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -25,8 +25,9 @@ UNIT_LUMEN: Final = "lm" PLATFORMS_GATEWAY: Final[list[str]] = [ Platform.BINARY_SENSOR, Platform.CLIMATE, - Platform.SENSOR, + Platform.NUMBER, Platform.SELECT, + Platform.SENSOR, Platform.SWITCH, ] ZEROCONF_MAP: Final[dict[str, str]] = { diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index a311151f645..f0e7f98d4b8 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.18.5"], + "requirements": ["plugwise==0.18.6"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/homeassistant/components/plugwise/number.py b/homeassistant/components/plugwise/number.py new file mode 100644 index 00000000000..6718c59c3c0 --- /dev/null +++ b/homeassistant/components/plugwise/number.py @@ -0,0 +1,115 @@ +"""Number platform for Plugwise integration.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass + +from plugwise import Smile + +from homeassistant.components.number import ( + NumberDeviceClass, + NumberEntity, + NumberEntityDescription, + NumberMode, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import TEMP_CELSIUS +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import PlugwiseDataUpdateCoordinator +from .entity import PlugwiseEntity + + +@dataclass +class PlugwiseEntityDescriptionMixin: + """Mixin values for Plugwse entities.""" + + command: Callable[[Smile, float], Awaitable[None]] + + +@dataclass +class PlugwiseNumberEntityDescription( + NumberEntityDescription, PlugwiseEntityDescriptionMixin +): + """Class describing Plugwise Number entities.""" + + +NUMBER_TYPES = ( + PlugwiseNumberEntityDescription( + key="maximum_boiler_temperature", + command=lambda api, value: api.set_max_boiler_temperature(value), + device_class=NumberDeviceClass.TEMPERATURE, + name="Maximum Boiler Temperature Setpoint", + entity_category=EntityCategory.CONFIG, + native_unit_of_measurement=TEMP_CELSIUS, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Plugwise number platform.""" + + coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ] + + entities: list[PlugwiseNumberEntity] = [] + for device_id, device in coordinator.data.devices.items(): + for description in NUMBER_TYPES: + if description.key in device: + entities.append( + PlugwiseNumberEntity(coordinator, device_id, description) + ) + + async_add_entities(entities) + + +class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity): + """Representation of a Plugwise number.""" + + entity_description: PlugwiseNumberEntityDescription + + def __init__( + self, + coordinator: PlugwiseDataUpdateCoordinator, + device_id: str, + description: PlugwiseNumberEntityDescription, + ) -> None: + """Initiate Plugwise Number.""" + super().__init__(coordinator, device_id) + self.entity_description = description + self._attr_unique_id = f"{device_id}-{description.key}" + self._attr_name = (f"{self.device['name']} {description.name}").lstrip() + self._attr_mode = NumberMode.BOX + + @property + def native_step(self) -> float: + """Return the setpoint step value.""" + return max(self.device["resolution"], 1) + + @property + def native_value(self) -> float: + """Return the present setpoint value.""" + return self.device[self.entity_description.key] + + @property + def native_min_value(self) -> float: + """Return the setpoint min. value.""" + return self.device["lower_bound"] + + @property + def native_max_value(self) -> float: + """Return the setpoint max. value.""" + return self.device["upper_bound"] + + async def async_set_native_value(self, value: float) -> None: + """Change to the new setpoint value.""" + await self.entity_description.command(self.coordinator.api, value) + await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index d84fa688c87..5dd7d0f871f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1254,7 +1254,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.18.5 +plugwise==0.18.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 57b73ced72e..d3bb3335510 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -862,7 +862,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.18.5 +plugwise==0.18.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json index 60bc4c35668..6fcb841cf3e 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump/all_data.json @@ -13,6 +13,9 @@ "model": "Generic heater", "name": "OpenTherm", "vendor": "Techneco", + "lower_bound": 0.0, + "upper_bound": 100.0, + "resolution": 1.0, "maximum_boiler_temperature": 60.0, "binary_sensors": { "dhw_state": false, diff --git a/tests/components/plugwise/test_number.py b/tests/components/plugwise/test_number.py new file mode 100644 index 00000000000..a4e084e5d3a --- /dev/null +++ b/tests/components/plugwise/test_number.py @@ -0,0 +1,40 @@ +"""Tests for the Plugwise Number integration.""" + +from unittest.mock import MagicMock + +from homeassistant.components.number import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_anna_number_entities( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: + """Test creation of a number.""" + state = hass.states.get("number.opentherm_maximum_boiler_temperature_setpoint") + assert state + assert float(state.state) == 60.0 + + +async def test_anna_max_boiler_temp_change( + hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry +) -> None: + """Test changing of number entities.""" + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.opentherm_maximum_boiler_temperature_setpoint", + ATTR_VALUE: 65, + }, + blocking=True, + ) + + assert mock_smile_anna.set_max_boiler_temperature.call_count == 1 + mock_smile_anna.set_max_boiler_temperature.assert_called_with(65)