diff --git a/README.md b/README.md index 69dcddf..ba2225e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Adapter -> ecoMAX ``` ### ecoNET 300 -While this integration is built on top of PyPlumIO library, which from the start was intended as ecoNET 300 alternative, **Patryk B** has originaly developed awesome HASS integration that communicates with ecoMAX controller via ecoNET 300 device. +While this integration is built on top of PyPlumIO library, which from the start was intended as ecoNET 300 alternative, **Patryk B** has originally developed awesome HASS integration that communicates with ecoMAX controller via ecoNET 300 device. Development of this integration has recently been picked up by @jontofront, so it's getting regular updates and fixes again! If you have an ecoNET 300 device, be sure to [check it out](https://github.com/jontofront/ecoNET-300-Home-Assistant-Integration)! @@ -172,7 +172,7 @@ Binary sensors have two states (on/off, running/not running, etc.). | Exhaust fan state | :white_check_mark: | :x: | #### Selects -Select entites can have multiple options to choose from. +Select entities can have multiple options to choose from. | Name | Options | ecoMAX pellet | ecoMAX installation | |-------------|:---------------:|:------------------:|:-------------------:| diff --git a/custom_components/plum_ecomax/__init__.py b/custom_components/plum_ecomax/__init__.py index 65a1ec6..69a5f02 100644 --- a/custom_components/plum_ecomax/__init__.py +++ b/custom_components/plum_ecomax/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import logging from typing import Final @@ -15,7 +16,7 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr from pyplumio.filters import custom, delta from pyplumio.structures.alerts import ATTR_ALERTS, Alert @@ -94,12 +95,14 @@ async def async_close_connection(event=None): def async_setup_events(hass: HomeAssistant, connection: EcomaxConnection) -> bool: """Set up the ecoMAX events.""" - dr = device_registry.async_get(hass) + device_registry = dr.async_get(hass) @callback async def async_dispatch_alert_events(alerts: list[Alert]) -> None: """Handle ecoMAX alert events.""" - if (device := dr.async_get_device({(DOMAIN, connection.uid)})) is None: + if ( + device := device_registry.async_get_device({(DOMAIN, connection.uid)}) + ) is None: _LOGGER.error("Device not found. uid: %s", connection.uid) return @@ -158,10 +161,8 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> config_entry.version = 3 if config_entry.version in (3, 4, 5): - try: + with suppress(KeyError): del data[CONF_CAPABILITIES] - except KeyError: - pass data[CONF_SUB_DEVICES] = await async_get_sub_devices(device) config_entry.version = 6 diff --git a/custom_components/plum_ecomax/binary_sensor.py b/custom_components/plum_ecomax/binary_sensor.py index 7f249f8..7930464 100644 --- a/custom_components/plum_ecomax/binary_sensor.py +++ b/custom_components/plum_ecomax/binary_sensor.py @@ -244,7 +244,7 @@ def async_setup_mixer_binary_sensors( """Set up the mixer binary sensors.""" entities: list[MixerBinarySensor] = [] - for index in connection.device.mixers.keys(): + for index in connection.device.mixers: entities.extend( MixerBinarySensor(connection, description, index) for description in get_by_modules( diff --git a/custom_components/plum_ecomax/button.py b/custom_components/plum_ecomax/button.py index 677dc89..ff78c6c 100644 --- a/custom_components/plum_ecomax/button.py +++ b/custom_components/plum_ecomax/button.py @@ -67,10 +67,10 @@ async def async_update(self, _) -> None: """Update entity state.""" async def async_added_to_hass(self): - """Called when an entity has their entity_id assigned.""" + """Subscribe to the events.""" async def async_will_remove_from_hass(self): - """Called when an entity is about to be removed.""" + """Unsubscribe from the events.""" async def async_setup_entry( diff --git a/custom_components/plum_ecomax/climate.py b/custom_components/plum_ecomax/climate.py index 4c06b37..bc2fcc7 100644 --- a/custom_components/plum_ecomax/climate.py +++ b/custom_components/plum_ecomax/climate.py @@ -159,11 +159,11 @@ async def async_update_target_temperature(self, value: float) -> None: @overload async def async_update_preset_mode(self, mode: int) -> None: - """Update preset mode from the state sensor.""" + ... @overload async def async_update_preset_mode(self, mode: ThermostatParameter) -> None: - """Update preset mode from the mode parameter.""" + ... async def async_update_preset_mode(self, mode) -> None: """Update preset mode.""" @@ -187,7 +187,7 @@ async def async_update_hvac_action(self, value: bool) -> None: self.async_write_ha_state() async def async_added_to_hass(self): - """Called when an entity has their entity_id assigned.""" + """Subscribe to thermostat events.""" callbacks = { "mode": on_change(self.async_update_preset_mode), "state": on_change(self.async_update_preset_mode), @@ -204,7 +204,7 @@ async def async_added_to_hass(self): self.device.subscribe(name, func) async def async_will_remove_from_hass(self): - """Called when an entity is about to be removed.""" + """Unsubscribe from thermostat events.""" self.device.unsubscribe("mode", self.async_update_preset_mode) self.device.unsubscribe("state", self.async_update_preset_mode) self.device.unsubscribe("contacts", self.async_update_hvac_action) diff --git a/custom_components/plum_ecomax/config_flow.py b/custom_components/plum_ecomax/config_flow.py index 877ce8f..ca9360e 100644 --- a/custom_components/plum_ecomax/config_flow.py +++ b/custom_components/plum_ecomax/config_flow.py @@ -91,6 +91,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 7 def __init__(self) -> None: + """Initialize a new config flow.""" self.connection: Connection | None = None self.device: AddressableDevice | None = None self.device_task: asyncio.Task | None = None @@ -314,9 +315,7 @@ class CannotConnect(HomeAssistantError): class TimeoutConnect(HomeAssistantError): - """Error to indicate that we established connection but - failed to see expected response in time. - """ + """Error to indicate that connection timed out.""" class UnsupportedProduct(HomeAssistantError): diff --git a/custom_components/plum_ecomax/connection.py b/custom_components/plum_ecomax/connection.py index cf39be8..52dbffc 100644 --- a/custom_components/plum_ecomax/connection.py +++ b/custom_components/plum_ecomax/connection.py @@ -138,16 +138,16 @@ def __getattr__(self, name: str): raise AttributeError async def async_setup(self) -> None: - """Setup connection and add hass stop handler.""" + """Set up ecoMAX connection.""" await self.connect() device: AddressableDevice = await self.get(ECOMAX, timeout=DEFAULT_TIMEOUT) - await device.wait_for(ATTR_LOADED, timeout=DEFAULT_TIMEOUT) - await device.wait_for(ATTR_SENSORS, timeout=DEFAULT_TIMEOUT) - await device.wait_for(ATTR_ECOMAX_PARAMETERS, timeout=DEFAULT_TIMEOUT) + for required in (ATTR_LOADED, ATTR_SENSORS, ATTR_ECOMAX_PARAMETERS): + await device.wait_for(required, timeout=DEFAULT_TIMEOUT) + self._device = device async def async_setup_thermostats(self) -> bool: - """Set up the thermostats.""" + """Set up thermostats.""" try: return await self.device.request( ATTR_THERMOSTAT_PARAMETERS, @@ -160,7 +160,7 @@ async def async_setup_thermostats(self) -> bool: return False async def async_setup_mixers(self) -> bool: - """Set up the mixers.""" + """Set up mixers.""" try: return await self.device.request( ATTR_MIXER_PARAMETERS, @@ -173,7 +173,7 @@ async def async_setup_mixers(self) -> bool: return False async def async_setup_regdata(self) -> bool: - """Setup regulator data.""" + """Set up regulator data.""" try: return await self.device.request( ATTR_REGDATA, diff --git a/custom_components/plum_ecomax/entity.py b/custom_components/plum_ecomax/entity.py index 732b71f..9d5be2b 100644 --- a/custom_components/plum_ecomax/entity.py +++ b/custom_components/plum_ecomax/entity.py @@ -22,7 +22,7 @@ class EcomaxEntity(ABC): entity_description: EntityDescription async def async_added_to_hass(self): - """Called when an entity has their entity_id assigned.""" + """Subscribe to events.""" async def async_set_available(_=None) -> None: self._attr_available = True @@ -36,7 +36,7 @@ async def async_set_available(_=None) -> None: await func(self.device.get_nowait(self.entity_description.key, None)) async def async_will_remove_from_hass(self): - """Called when an entity is about to be removed.""" + """Unsubscribe from events.""" self.device.unsubscribe(self.entity_description.key, self.async_update) @property @@ -88,9 +88,7 @@ def should_poll(self) -> bool: @property def has_entity_name(self) -> bool: - """Return if the name of the entity is describing only the - entity itself. - """ + """Return if the name of the entity is describing only the entity itself.""" return True @abstractmethod diff --git a/custom_components/plum_ecomax/number.py b/custom_components/plum_ecomax/number.py index a5ba626..93951c6 100644 --- a/custom_components/plum_ecomax/number.py +++ b/custom_components/plum_ecomax/number.py @@ -258,8 +258,7 @@ def async_setup_ecomax_numbers(connection: EcomaxConnection) -> list[EcomaxNumbe def async_setup_mixer_numbers(connection: EcomaxConnection) -> list[MixerNumber]: """Set up the mixer numbers.""" entities: list[MixerNumber] = [] - - for index in connection.device.mixers.keys(): + for index in connection.device.mixers: entities.extend( MixerNumber(connection, description, index) for description in get_by_index( diff --git a/custom_components/plum_ecomax/select.py b/custom_components/plum_ecomax/select.py index b6ff583..0259702 100644 --- a/custom_components/plum_ecomax/select.py +++ b/custom_components/plum_ecomax/select.py @@ -161,8 +161,7 @@ def async_setup_ecomax_selects(connection: EcomaxConnection) -> list[EcomaxSelec def async_setup_mixer_selects(connection: EcomaxConnection) -> list[MixerSelect]: """Set up the mixer selects.""" entities: list[MixerSelect] = [] - - for index in connection.device.mixers.keys(): + for index in connection.device.mixers: entities.extend( MixerSelect(connection, description, index) for description in get_by_index( diff --git a/custom_components/plum_ecomax/sensor.py b/custom_components/plum_ecomax/sensor.py index a55bf16..6c70dc8 100644 --- a/custom_components/plum_ecomax/sensor.py +++ b/custom_components/plum_ecomax/sensor.py @@ -457,7 +457,7 @@ class EcomaxMeter(RestoreSensor, EcomaxSensor): _unrecorded_attributes: frozenset[str] = frozenset({ATTR_BURNED_SINCE_LAST_UPDATE}) async def async_added_to_hass(self): - """Called when an entity has their entity_id assigned.""" + """Restore native value.""" await super().async_added_to_hass() if (last_sensor_data := await self.async_get_last_sensor_data()) is not None: self._attr_native_value = last_sensor_data.native_value @@ -537,7 +537,7 @@ async def async_update(self, value: dict[str, Any]) -> None: self.async_write_ha_state() async def async_added_to_hass(self): - """Called when an entity has their entity_id assigned.""" + """Subscribe to regdata event.""" async def async_set_available(regdata: dict[str, Any]) -> None: if self.entity_description.key in regdata: @@ -552,7 +552,7 @@ async def async_set_available(regdata: dict[str, Any]) -> None: await func(self.device.data[ATTR_REGDATA]) async def async_will_remove_from_hass(self): - """Called when an entity is about to be removed.""" + """Unsubscribe from regdata event.""" self.device.unsubscribe(ATTR_REGDATA, self.async_update) @property @@ -632,10 +632,10 @@ def async_setup_regdata_sensors(connection: EcomaxConnection) -> list[RegdataSen def async_setup_mixer_sensors(connection: EcomaxConnection) -> list[MixerSensor]: + """Set up the mixer sensors.""" """Set up the mixer sensors.""" entities: list[MixerSensor] = [] - - for index in connection.device.mixers.keys(): + for index in connection.device.mixers: entities.extend( MixerSensor(connection, description, index) for description in get_by_modules( diff --git a/custom_components/plum_ecomax/services.py b/custom_components/plum_ecomax/services.py index 878fa83..00d7c40 100644 --- a/custom_components/plum_ecomax/services.py +++ b/custom_components/plum_ecomax/services.py @@ -15,7 +15,7 @@ callback, ) from homeassistant.exceptions import HomeAssistantError, ServiceValidationError -from homeassistant.helpers import device_registry, entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.service import ( @@ -103,8 +103,8 @@ def async_extract_target_device( device_id: str, hass: HomeAssistant, connection: EcomaxConnection ) -> Device: """Get target device by the device id.""" - dr = device_registry.async_get(hass) - device = dr.async_get(device_id) + device_registry = dr.async_get(hass) + device = device_registry.async_get(device_id) if not device: raise HomeAssistantError( translation_domain=DOMAIN, @@ -131,10 +131,10 @@ def async_extract_referenced_devices( """Extract referenced devices from the selected entities.""" devices: set[Device] = set() extracted: set[str] = set() - ent_reg = entity_registry.async_get(hass) + entity_registry = er.async_get(hass) referenced = selected.referenced | selected.indirectly_referenced for entity_id in referenced: - entity = ent_reg.async_get(entity_id) + entity = entity_registry.async_get(entity_id) if entity.device_id not in extracted: devices.add(async_extract_target_device(entity.device_id, hass, connection)) extracted.add(entity.device_id) diff --git a/custom_components/plum_ecomax/switch.py b/custom_components/plum_ecomax/switch.py index 9d94080..9f8f8d9 100644 --- a/custom_components/plum_ecomax/switch.py +++ b/custom_components/plum_ecomax/switch.py @@ -209,9 +209,9 @@ def async_setup_ecomax_switches(connection: EcomaxConnection) -> list[EcomaxSwit def async_setup_mixer_switches(connection: EcomaxConnection) -> list[MixerSwitch]: """Set up the mixers switches.""" + """Set up the mixer sensors.""" entities: list[MixerSwitch] = [] - - for index in connection.device.mixers.keys(): + for index in connection.device.mixers: entities.extend( MixerSwitch(connection, description, index) for description in get_by_index( diff --git a/custom_components/plum_ecomax/water_heater.py b/custom_components/plum_ecomax/water_heater.py index c817496..34c6ed5 100644 --- a/custom_components/plum_ecomax/water_heater.py +++ b/custom_components/plum_ecomax/water_heater.py @@ -134,7 +134,7 @@ async def async_update(self, value) -> None: self.async_write_ha_state() async def async_added_to_hass(self): - """Called when an entity has their entity_id assigned.""" + """Subscribe to water heater events.""" key = self.entity_description.key callbacks = { f"{key}_temp": throttle(on_change(self.async_update), seconds=10), @@ -151,7 +151,7 @@ async def async_added_to_hass(self): self.device.subscribe(name, func) async def async_will_remove_from_hass(self): - """Called when an entity is about to be removed.""" + """Unsubscribe to water heater events.""" key = self.entity_description.key self.device.unsubscribe(f"{key}_temp", self.async_update) self.device.unsubscribe(f"{key}_target_temp", self.async_update_target_temp) diff --git a/tests/conftest.py b/tests/conftest.py index 5ba27d0..79e031a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -138,7 +138,7 @@ def fixture_serial_config_data(serial_user_input, config_data): @pytest.fixture async def setup_integration(): - """Setup the integration.""" + """Set up the integration.""" async def setup_entry(hass: HomeAssistant, config_entry: MockConfigEntry): config_entry.add_to_hass(hass) @@ -185,7 +185,7 @@ def connected(): @pytest.fixture(name="ecomax_base") def fixture_ecomax_base() -> EcoMAX: - """Basic ecoMAX device with no data.""" + """Return base ecoMAX device with no data.""" ecomax = EcoMAX(queue=Mock(), network=NetworkInfo()) with patch( "custom_components.plum_ecomax.connection.EcomaxConnection.device", ecomax @@ -226,7 +226,7 @@ def fixture_ecomax_common(ecomax_base: EcoMAX): @pytest.fixture def ecomax_control(ecomax_common: EcoMAX): - """Inject ecomax control parameter""" + """Inject ecomax control parameter.""" ecomax_common.data.update( { ATTR_ECOMAX_CONTROL: EcomaxBinaryParameter( @@ -397,6 +397,7 @@ def fixture_ecomax_i(ecomax_common: EcoMAX): @pytest.fixture() def ecomax_860p3_o(ecomax_p: EcoMAX): """Inject data for ecoMAX 860P3-O. + (product_type: 0, product_id: 51) """ product_type = ProductType.ECOMAX_P @@ -426,6 +427,7 @@ def ecomax_860p3_o(ecomax_p: EcoMAX): @pytest.fixture() def ecomax_860p3_s_lite(ecomax_p: EcoMAX): """Inject data for ecoMAX 860P3-S Lite. + (product_type: 0, product_id: 51) """ product_type = ProductType.ECOMAX_P diff --git a/tests/test_climate.py b/tests/test_climate.py index c9ea72e..8d18b50 100644 --- a/tests/test_climate.py +++ b/tests/test_climate.py @@ -71,7 +71,7 @@ def fixture_frozen_time(): @pytest.fixture(name="async_set_preset_mode") async def fixture_async_set_preset_mode(): - """Sets the climate preset mode.""" + """Set the climate preset mode.""" async def async_set_preset_mode( hass: HomeAssistant, entity_id: str, preset_mode: str @@ -89,7 +89,7 @@ async def async_set_preset_mode( @pytest.fixture(name="async_set_temperature") async def fixture_async_set_temperature(): - """Sets the climate temperature.""" + """Set the climate temperature.""" async def async_set_temperature( hass: HomeAssistant, entity_id: str, temperature: float diff --git a/tests/test_number.py b/tests/test_number.py index f772baf..ba13121 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -54,7 +54,7 @@ def set_connected(connected): @pytest.fixture(name="async_set_value") async def fixture_async_set_value(): - """Sets the value.""" + """Set the value.""" async def async_set_value(hass: HomeAssistant, entity_id: str, value: float): await hass.services.async_call( diff --git a/tests/test_select.py b/tests/test_select.py index 6e8900d..cf8ff98 100644 --- a/tests/test_select.py +++ b/tests/test_select.py @@ -59,7 +59,7 @@ def set_connected(connected): @pytest.fixture(name="async_select_option") async def fixture_async_select_option(): - """Selects the option.""" + """Select the option.""" async def async_select_option(hass: HomeAssistant, entity_id: str, option: str): await hass.services.async_call( diff --git a/tests/test_switch.py b/tests/test_switch.py index 284b7c6..4d5a477 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -50,7 +50,7 @@ def set_connected(connected): @pytest.fixture(name="async_turn_on") async def fixture_async_turn_on(): - """Turns switch on.""" + """Turn switch on.""" async def async_turn_on(hass: HomeAssistant, entity_id: str): await hass.services.async_call( @@ -66,7 +66,7 @@ async def async_turn_on(hass: HomeAssistant, entity_id: str): @pytest.fixture(name="async_turn_off") async def fixture_async_turn_off(): - """Turns switch off.""" + """Turn switch off.""" async def async_turn_off(hass: HomeAssistant, entity_id: str): await hass.services.async_call( diff --git a/tests/test_water_heater.py b/tests/test_water_heater.py index 013b2a6..3aa36ac 100644 --- a/tests/test_water_heater.py +++ b/tests/test_water_heater.py @@ -65,7 +65,7 @@ def fixture_frozen_time(): @pytest.fixture(name="async_set_operation_mode") async def fixture_async_set_operation_mode(): - """Sets the water heater operation mode.""" + """Set the water heater operation mode.""" async def async_set_operation_mode( hass: HomeAssistant, entity_id: str, operation_mode: str @@ -83,7 +83,7 @@ async def async_set_operation_mode( @pytest.fixture(name="async_set_temperature") async def fixture_async_set_temperature(): - """Sets the water heater temperature.""" + """Set the water heater temperature.""" async def async_set_temperature( hass: HomeAssistant, entity_id: str, temperature: float