diff --git a/homeassistant/components/nibe_heatpump/__init__.py b/homeassistant/components/nibe_heatpump/__init__.py index 57c5f680e01..fd77b5e2344 100644 --- a/homeassistant/components/nibe_heatpump/__init__.py +++ b/homeassistant/components/nibe_heatpump/__init__.py @@ -8,11 +8,11 @@ from datetime import timedelta from functools import cached_property from typing import Any, Generic, TypeVar -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from nibe.connection import Connection from nibe.connection.modbus import Modbus from nibe.connection.nibegw import NibeGW, ProductInfo -from nibe.exceptions import CoilNotFoundException, CoilReadException +from nibe.exceptions import CoilNotFoundException, ReadException from nibe.heatpump import HeatPump, Model, Series from homeassistant.config_entries import ConfigEntry @@ -182,7 +182,7 @@ class ContextCoordinator( return release_update -class Coordinator(ContextCoordinator[dict[int, Coil], int]): +class Coordinator(ContextCoordinator[dict[int, CoilData], int]): """Update coordinator for nibe heat pumps.""" config_entry: ConfigEntry @@ -199,17 +199,18 @@ class Coordinator(ContextCoordinator[dict[int, Coil], int]): ) self.data = {} - self.seed: dict[int, Coil] = {} + self.seed: dict[int, CoilData] = {} self.connection = connection self.heatpump = heatpump self.task: asyncio.Task | None = None heatpump.subscribe(heatpump.COIL_UPDATE_EVENT, self._on_coil_update) - def _on_coil_update(self, coil: Coil): + def _on_coil_update(self, data: CoilData): """Handle callback on coil updates.""" - self.data[coil.address] = coil - self.seed[coil.address] = coil + coil = data.coil + self.data[coil.address] = data + self.seed[coil.address] = data self.async_update_context_listeners([coil.address]) @property @@ -246,26 +247,26 @@ class Coordinator(ContextCoordinator[dict[int, Coil], int]): async def async_write_coil(self, coil: Coil, value: int | float | str) -> None: """Write coil and update state.""" - coil.value = value - coil = await self.connection.write_coil(coil) + data = CoilData(coil, value) + await self.connection.write_coil(data) - self.data[coil.address] = coil + self.data[coil.address] = data self.async_update_context_listeners([coil.address]) - async def async_read_coil(self, coil: Coil) -> Coil: + async def async_read_coil(self, coil: Coil) -> CoilData: """Read coil and update state using callbacks.""" return await self.connection.read_coil(coil) - async def _async_update_data(self) -> dict[int, Coil]: + async def _async_update_data(self) -> dict[int, CoilData]: self.task = asyncio.current_task() try: return await self._async_update_data_internal() finally: self.task = None - async def _async_update_data_internal(self) -> dict[int, Coil]: - result: dict[int, Coil] = {} + async def _async_update_data_internal(self) -> dict[int, CoilData]: + result: dict[int, CoilData] = {} def _get_coils() -> Iterable[Coil]: for address in sorted(self.context_callbacks.keys()): @@ -282,10 +283,10 @@ class Coordinator(ContextCoordinator[dict[int, Coil], int]): yield coil try: - async for coil in self.connection.read_coils(_get_coils()): - result[coil.address] = coil - self.seed.pop(coil.address, None) - except CoilReadException as exception: + async for data in self.connection.read_coils(_get_coils()): + result[data.coil.address] = data + self.seed.pop(data.coil.address, None) + except ReadException as exception: if not result: raise UpdateFailed(f"Failed to update: {exception}") from exception self.logger.debug( @@ -329,7 +330,7 @@ class CoilEntity(CoordinatorEntity[Coordinator]): self.coordinator.data or {} ) - def _async_read_coil(self, coil: Coil): + def _async_read_coil(self, data: CoilData): """Update state of entity based on coil data.""" async def _async_write_coil(self, value: int | float | str): @@ -337,10 +338,9 @@ class CoilEntity(CoordinatorEntity[Coordinator]): await self.coordinator.async_write_coil(self._coil, value) def _handle_coordinator_update(self) -> None: - coil = self.coordinator.data.get(self._coil.address) - if coil is None: + data = self.coordinator.data.get(self._coil.address) + if data is None: return - self._coil = coil - self._async_read_coil(coil) + self._async_read_coil(data) self.async_write_ha_state() diff --git a/homeassistant/components/nibe_heatpump/binary_sensor.py b/homeassistant/components/nibe_heatpump/binary_sensor.py index 89c993cafaa..263fd41b309 100644 --- a/homeassistant/components/nibe_heatpump/binary_sensor.py +++ b/homeassistant/components/nibe_heatpump/binary_sensor.py @@ -1,7 +1,7 @@ """The Nibe Heat Pump binary sensors.""" from __future__ import annotations -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -37,5 +37,5 @@ class BinarySensor(CoilEntity, BinarySensorEntity): """Initialize entity.""" super().__init__(coordinator, coil, ENTITY_ID_FORMAT) - def _async_read_coil(self, coil: Coil) -> None: - self._attr_is_on = coil.value == "ON" + def _async_read_coil(self, data: CoilData) -> None: + self._attr_is_on = data.value == "ON" diff --git a/homeassistant/components/nibe_heatpump/config_flow.py b/homeassistant/components/nibe_heatpump/config_flow.py index 6050010b20d..434a9a50ea6 100644 --- a/homeassistant/components/nibe_heatpump/config_flow.py +++ b/homeassistant/components/nibe_heatpump/config_flow.py @@ -8,10 +8,10 @@ from nibe.connection.nibegw import NibeGW from nibe.exceptions import ( AddressInUseException, CoilNotFoundException, - CoilReadException, - CoilReadSendException, - CoilWriteException, CoilWriteSendException, + ReadException, + ReadSendException, + WriteException, ) from nibe.heatpump import HeatPump, Model import voluptuous as vol @@ -108,13 +108,13 @@ async def validate_nibegw_input( try: await connection.verify_connectivity() - except (CoilReadSendException, CoilWriteSendException) as exception: + except (ReadSendException, CoilWriteSendException) as exception: raise FieldError(str(exception), CONF_IP_ADDRESS, "address") from exception except CoilNotFoundException as exception: raise FieldError("Coils not found", "base", "model") from exception - except CoilReadException as exception: + except ReadException as exception: raise FieldError("Timeout on read from pump", "base", "read") from exception - except CoilWriteException as exception: + except WriteException as exception: raise FieldError("Timeout on writing to pump", "base", "write") from exception finally: await connection.stop() @@ -147,13 +147,13 @@ async def validate_modbus_input( try: await connection.verify_connectivity() - except (CoilReadSendException, CoilWriteSendException) as exception: + except (ReadSendException, CoilWriteSendException) as exception: raise FieldError(str(exception), CONF_MODBUS_URL, "address") from exception except CoilNotFoundException as exception: raise FieldError("Coils not found", "base", "model") from exception - except CoilReadException as exception: + except ReadException as exception: raise FieldError("Timeout on read from pump", "base", "read") from exception - except CoilWriteException as exception: + except WriteException as exception: raise FieldError("Timeout on writing to pump", "base", "write") from exception finally: await connection.stop() diff --git a/homeassistant/components/nibe_heatpump/manifest.json b/homeassistant/components/nibe_heatpump/manifest.json index d9a2bd365e9..5114cc222e9 100644 --- a/homeassistant/components/nibe_heatpump/manifest.json +++ b/homeassistant/components/nibe_heatpump/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nibe_heatpump", "iot_class": "local_polling", - "requirements": ["nibe==1.6.0"] + "requirements": ["nibe==2.0.0"] } diff --git a/homeassistant/components/nibe_heatpump/number.py b/homeassistant/components/nibe_heatpump/number.py index 579b8e79156..79078811881 100644 --- a/homeassistant/components/nibe_heatpump/number.py +++ b/homeassistant/components/nibe_heatpump/number.py @@ -1,7 +1,7 @@ """The Nibe Heat Pump numbers.""" from __future__ import annotations -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from homeassistant.components.number import ENTITY_ID_FORMAT, NumberEntity from homeassistant.config_entries import ConfigEntry @@ -58,13 +58,13 @@ class Number(CoilEntity, NumberEntity): self._attr_native_unit_of_measurement = coil.unit self._attr_native_value = None - def _async_read_coil(self, coil: Coil) -> None: - if coil.value is None: + def _async_read_coil(self, data: CoilData) -> None: + if data.value is None: self._attr_native_value = None return try: - self._attr_native_value = float(coil.value) + self._attr_native_value = float(data.value) except ValueError: self._attr_native_value = None diff --git a/homeassistant/components/nibe_heatpump/select.py b/homeassistant/components/nibe_heatpump/select.py index d554eaf4ff0..e255ff36500 100644 --- a/homeassistant/components/nibe_heatpump/select.py +++ b/homeassistant/components/nibe_heatpump/select.py @@ -1,7 +1,7 @@ """The Nibe Heat Pump select.""" from __future__ import annotations -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from homeassistant.components.select import ENTITY_ID_FORMAT, SelectEntity from homeassistant.config_entries import ConfigEntry @@ -40,12 +40,12 @@ class Select(CoilEntity, SelectEntity): self._attr_options = list(coil.mappings.values()) self._attr_current_option = None - def _async_read_coil(self, coil: Coil) -> None: - if not isinstance(coil.value, str): + def _async_read_coil(self, data: CoilData) -> None: + if not isinstance(data.value, str): self._attr_current_option = None return - self._attr_current_option = coil.value + self._attr_current_option = data.value async def async_select_option(self, option: str) -> None: """Support writing value.""" diff --git a/homeassistant/components/nibe_heatpump/sensor.py b/homeassistant/components/nibe_heatpump/sensor.py index 94f37040486..8aabad2c9fc 100644 --- a/homeassistant/components/nibe_heatpump/sensor.py +++ b/homeassistant/components/nibe_heatpump/sensor.py @@ -1,7 +1,7 @@ """The Nibe Heat Pump sensors.""" from __future__ import annotations -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from homeassistant.components.sensor import ( ENTITY_ID_FORMAT, @@ -146,5 +146,5 @@ class Sensor(CoilEntity, SensorEntity): self._attr_native_unit_of_measurement = coil.unit self._attr_entity_category = EntityCategory.DIAGNOSTIC - def _async_read_coil(self, coil: Coil): - self._attr_native_value = coil.value + def _async_read_coil(self, data: CoilData): + self._attr_native_value = data.value diff --git a/homeassistant/components/nibe_heatpump/switch.py b/homeassistant/components/nibe_heatpump/switch.py index 23634e77c52..95d96de9764 100644 --- a/homeassistant/components/nibe_heatpump/switch.py +++ b/homeassistant/components/nibe_heatpump/switch.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -40,8 +40,8 @@ class Switch(CoilEntity, SwitchEntity): super().__init__(coordinator, coil, ENTITY_ID_FORMAT) self._attr_is_on = None - def _async_read_coil(self, coil: Coil) -> None: - self._attr_is_on = coil.value == "ON" + def _async_read_coil(self, data: CoilData) -> None: + self._attr_is_on = data.value == "ON" async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" diff --git a/requirements_all.txt b/requirements_all.txt index 4d903ffaaef..10dfef7810a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1201,7 +1201,7 @@ nextcord==2.0.0a8 nextdns==1.3.0 # homeassistant.components.nibe_heatpump -nibe==1.6.0 +nibe==2.0.0 # homeassistant.components.niko_home_control niko-home-control==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ecda31f4c36..3b67a588729 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -891,7 +891,7 @@ nextcord==2.0.0a8 nextdns==1.3.0 # homeassistant.components.nibe_heatpump -nibe==1.6.0 +nibe==2.0.0 # homeassistant.components.nfandroidtv notifications-android-tv==0.1.5 diff --git a/tests/components/nibe_heatpump/conftest.py b/tests/components/nibe_heatpump/conftest.py index 0ae9c73e6b8..b75c49b2b79 100644 --- a/tests/components/nibe_heatpump/conftest.py +++ b/tests/components/nibe_heatpump/conftest.py @@ -4,9 +4,9 @@ from contextlib import ExitStack from typing import Any from unittest.mock import AsyncMock, Mock, patch -from nibe.coil import Coil +from nibe.coil import Coil, CoilData from nibe.connection import Connection -from nibe.exceptions import CoilReadException +from nibe.exceptions import ReadException import pytest @@ -39,12 +39,11 @@ async def fixture_coils(mock_connection): """Return a dict with coil data.""" coils: dict[int, Any] = {} - async def read_coil(coil: Coil, timeout: float = 0) -> Coil: + async def read_coil(coil: Coil, timeout: float = 0) -> CoilData: nonlocal coils if (data := coils.get(coil.address, None)) is None: - raise CoilReadException() - coil.value = data - return coil + raise ReadException() + return CoilData(coil, data) async def read_coils( coils: Iterable[Coil], timeout: float = 0 diff --git a/tests/components/nibe_heatpump/test_button.py b/tests/components/nibe_heatpump/test_button.py index 0ced1799b48..e4f90a59f67 100644 --- a/tests/components/nibe_heatpump/test_button.py +++ b/tests/components/nibe_heatpump/test_button.py @@ -3,7 +3,7 @@ from typing import Any from unittest.mock import AsyncMock, patch from freezegun.api import FrozenDateTimeFactory -from nibe.coil import Coil +from nibe.coil import CoilData from nibe.coil_groups import UNIT_COILGROUPS from nibe.heatpump import Model import pytest @@ -91,6 +91,6 @@ async def test_reset_button( # Verify reset was written args = mock_connection.write_coil.call_args assert args - coil: Coil = args.args[0] - assert coil.address == unit.alarm_reset + coil: CoilData = args.args[0] + assert coil.coil.address == unit.alarm_reset assert coil.value == 1 diff --git a/tests/components/nibe_heatpump/test_config_flow.py b/tests/components/nibe_heatpump/test_config_flow.py index 9263919214d..3360c82577f 100644 --- a/tests/components/nibe_heatpump/test_config_flow.py +++ b/tests/components/nibe_heatpump/test_config_flow.py @@ -5,9 +5,9 @@ from nibe.coil import Coil from nibe.exceptions import ( AddressInUseException, CoilNotFoundException, - CoilReadException, - CoilReadSendException, - CoilWriteException, + ReadException, + ReadSendException, + WriteException, ) import pytest @@ -169,7 +169,7 @@ async def test_read_timeout( """Test we handle cannot connect error.""" result = await _get_connection_form(hass, connection_type) - mock_connection.verify_connectivity.side_effect = CoilReadException() + mock_connection.verify_connectivity.side_effect = ReadException() result2 = await hass.config_entries.flow.async_configure(result["flow_id"], data) @@ -190,7 +190,7 @@ async def test_write_timeout( """Test we handle cannot connect error.""" result = await _get_connection_form(hass, connection_type) - mock_connection.verify_connectivity.side_effect = CoilWriteException() + mock_connection.verify_connectivity.side_effect = WriteException() result2 = await hass.config_entries.flow.async_configure(result["flow_id"], data) @@ -232,7 +232,7 @@ async def test_nibegw_invalid_host( """Test we handle cannot connect error.""" result = await _get_connection_form(hass, connection_type) - mock_connection.verify_connectivity.side_effect = CoilReadSendException() + mock_connection.verify_connectivity.side_effect = ReadSendException() result2 = await hass.config_entries.flow.async_configure(result["flow_id"], data)