diff --git a/.coveragerc b/.coveragerc index da6ae8b4cc1..cb42ac67231 100644 --- a/.coveragerc +++ b/.coveragerc @@ -838,6 +838,7 @@ omit = homeassistant/components/nfandroidtv/__init__.py homeassistant/components/nfandroidtv/notify.py homeassistant/components/nibe_heatpump/__init__.py + homeassistant/components/nibe_heatpump/number.py homeassistant/components/nibe_heatpump/select.py homeassistant/components/nibe_heatpump/sensor.py homeassistant/components/nibe_heatpump/binary_sensor.py diff --git a/homeassistant/components/nibe_heatpump/__init__.py b/homeassistant/components/nibe_heatpump/__init__.py index ed3bc649453..35c43f85543 100644 --- a/homeassistant/components/nibe_heatpump/__init__.py +++ b/homeassistant/components/nibe_heatpump/__init__.py @@ -39,7 +39,12 @@ from .const import ( LOGGER, ) -PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SELECT, Platform.SENSOR] +PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.NUMBER, + Platform.SELECT, + Platform.SENSOR, +] COIL_READ_RETRIES = 5 diff --git a/homeassistant/components/nibe_heatpump/number.py b/homeassistant/components/nibe_heatpump/number.py new file mode 100644 index 00000000000..11c6917ec1c --- /dev/null +++ b/homeassistant/components/nibe_heatpump/number.py @@ -0,0 +1,68 @@ +"""The Nibe Heat Pump numbers.""" +from __future__ import annotations + +from nibe.coil import Coil + +from homeassistant.components.number import ENTITY_ID_FORMAT, NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import DOMAIN, CoilEntity, Coordinator + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up platform.""" + + coordinator: Coordinator = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities( + Number(coordinator, coil) + for coil in coordinator.coils + if coil.is_writable and not coil.mappings + ) + + +def _get_numeric_limits(size: str): + """Calculate the integer limits of a signed or unsigned integer value.""" + if size[0] == "u": + return (0, pow(2, int(size[1:])) - 1) + if size[0] == "s": + return (-pow(2, int(size[1:]) - 1), pow(2, int(size[1:]) - 1) - 1) + raise ValueError(f"Invalid size type specified {size}") + + +class Number(CoilEntity, NumberEntity): + """Number entity.""" + + _attr_entity_category = EntityCategory.CONFIG + + def __init__(self, coordinator: Coordinator, coil: Coil) -> None: + """Initialize entity.""" + super().__init__(coordinator, coil, ENTITY_ID_FORMAT) + if coil.min is None or coil.max is None: + ( + self._attr_native_min_value, + self._attr_native_max_value, + ) = _get_numeric_limits(coil.size) + else: + self._attr_native_min_value = float(coil.min) + self._attr_native_max_value = float(coil.max) + + self._attr_native_unit_of_measurement = coil.unit + self._attr_native_value = None + + def _async_read_coil(self, coil: Coil) -> None: + try: + self._attr_native_value = float(coil.value) + except ValueError: + self._attr_native_value = None + + async def async_set_native_value(self, value: float) -> None: + """Set new value.""" + await self._async_write_coil(value)