From ed2f70d3221d0110083579e9b13229fed7f1ceab Mon Sep 17 00:00:00 2001 From: thomasgermain <12560542+thomasgermain@users.noreply.github.com> Date: Sun, 20 Jun 2021 16:41:05 +0200 Subject: [PATCH] v1.7.0b1 --- README.md | 8 +- custom_components/multimatic/binary_sensor.py | 80 ++++++++----------- custom_components/multimatic/climate.py | 53 +++++++----- custom_components/multimatic/config_flow.py | 2 +- custom_components/multimatic/const.py | 1 - custom_components/multimatic/coordinator.py | 37 ++++++--- custom_components/multimatic/entities.py | 5 +- custom_components/multimatic/fan.py | 16 ++-- custom_components/multimatic/manifest.json | 4 +- custom_components/multimatic/sensor.py | 31 ++++--- custom_components/multimatic/water_heater.py | 5 +- 11 files changed, 134 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 574908f..4cf66ac 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ You can do some find and replace (e.g. `climate.vaillant_bathroom`-> `climate.ba - Integration into the HACS default repositories - Special thanks to [@vit-](https://github.com/vit-) -### [1.7.0b](https://github.com/thomasgermain/vaillant-component/releases/tag/1.7.0b0) +### [1.7.0b0](https://github.com/thomasgermain/vaillant-component/releases/tag/1.7.0b0) - **BREAKING CHANGE**: improvements of the `unique_id` of some entities (but not the `entity_id`). You may have to delete old entities which will be unavailable and change the `entity_id` of newly created entities (because they may have `entity_id` like xxx_2) @@ -117,6 +117,12 @@ You can do some find and replace (e.g. `climate.vaillant_bathroom`-> `climate.ba - Better handling and logging in case of error - Update pymultimatic to 0.4.x (= improvement in handling wrong responses coming from the API) +### [1.7.0b1](https://github.com/thomasgermain/vaillant-component/releases/tag/1.7.0b1) +- Code quality improvement +- Remove some incorrect `device_class` +- None safe `name`, `unit_of_measurement`, etc. +- Better logging in case of error + ## Provided entities - 1 water_heater entity, if any water heater: `water_heater.`, basically `water_heater.control_dhw` - 1 climate entity per zone (expect if the zone is controlled by room) `climate.` diff --git a/custom_components/multimatic/binary_sensor.py b/custom_components/multimatic/binary_sensor.py index 469ec87..c828be1 100644 --- a/custom_components/multimatic/binary_sensor.py +++ b/custom_components/multimatic/binary_sensor.py @@ -8,7 +8,6 @@ DEVICE_CLASS_BATTERY, DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_LOCK, - DEVICE_CLASS_POWER, DEVICE_CLASS_PROBLEM, DEVICE_CLASS_WINDOW, DOMAIN, @@ -67,28 +66,28 @@ async def async_setup_entry(hass, entry, async_add_entities): class CirculationSensor(MultimaticEntity, BinarySensorEntity): """Binary sensor for circulation running on or not.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Initialize entity.""" super().__init__(coordinator, DOMAIN, "dhw_circulation") - - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return DEVICE_CLASS_POWER + self._name = coordinator.data.dhw.circulation.name @property def is_on(self): """Return true if the binary sensor is on.""" + a_mode = self.active_mode return ( - self.active_mode.current == OperatingModes.ON - or self.active_mode.sub == SettingModes.ON - or self.active_mode.current == QuickModes.HOTWATER_BOOST + a_mode.current in (OperatingModes.ON, QuickModes.HOTWATER_BOOST) + or a_mode.sub == SettingModes.ON ) @property def available(self): """Return True if entity is available.""" - return super().available and self.circulation is not None + return ( + super().available + and self.coordinator.data.dhw is not None + and self.coordinator.data.dhw.circulation is not None + ) @property def active_mode(self): @@ -98,18 +97,15 @@ def active_mode(self): @property def name(self) -> str: """Return the name of the entity.""" - return self.circulation.name if self.circulation else None - - @property - def circulation(self): - """Return the circulation.""" - return self.coordinator.data.dhw.circulation + return self._name class RoomWindow(MultimaticEntity, BinarySensorEntity): """multimatic window binary sensor.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator, room: Room): + def __init__( + self, coordinator: MultimaticDataUpdateCoordinator, room: Room + ) -> None: """Initialize entity.""" super().__init__(coordinator, DOMAIN, f"{room.name}_{DEVICE_CLASS_WINDOW}") self._room_id = room.id @@ -193,7 +189,7 @@ class RoomDeviceChildLock(RoomDeviceEntity): def __init__( self, coordinator: MultimaticDataUpdateCoordinator, device: Device, room: Room - ): + ) -> None: """Initialize entity.""" super().__init__(coordinator, device, DEVICE_CLASS_LOCK) self._room_id = room.id @@ -222,7 +218,9 @@ def device_class(self): class RoomDeviceBattery(RoomDeviceEntity): """Represent a device battery.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator, device: Device): + def __init__( + self, coordinator: MultimaticDataUpdateCoordinator, device: Device + ) -> None: """Initialize entity.""" super().__init__(coordinator, device, DEVICE_CLASS_BATTERY) @@ -240,7 +238,9 @@ def device_class(self): class RoomDeviceConnectivity(RoomDeviceEntity): """Device in room is out of reach or not.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator, device: Device): + def __init__( + self, coordinator: MultimaticDataUpdateCoordinator, device: Device + ) -> None: """Initialize entity.""" super().__init__(coordinator, device, DEVICE_CLASS_CONNECTIVITY) @@ -297,7 +297,7 @@ def system_info(self): class BoxUpdate(VRBoxEntity): """Update binary sensor.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Init.""" super().__init__( coordinator, @@ -309,16 +309,11 @@ def is_on(self): """Return true if the binary sensor is on.""" return not self.coordinator.data.info.is_up_to_date - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return DEVICE_CLASS_POWER - class BoxOnline(VRBoxEntity): """Check if box is online.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Init.""" super().__init__(coordinator, "multimatic_system_online") @@ -341,7 +336,7 @@ def device_class(self): class BoilerStatus(MultimaticEntity, BinarySensorEntity): """Check if there is some error.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Initialize entity.""" MultimaticEntity.__init__( self, @@ -350,6 +345,7 @@ def __init__(self, coordinator: MultimaticDataUpdateCoordinator): coordinator.data.boiler_status.device_name, ) self._boiler_id = slugify(coordinator.data.boiler_status.device_name) + self._name = coordinator.data.boiler_status.device_name @property def is_on(self): @@ -372,15 +368,15 @@ def device_info(self): "identifiers": { (MULTIMATIC, self._boiler_id, self.coordinator.data.info.serial_number) }, - "name": self.boiler_status.device_name, + "name": self._name, "manufacturer": "Vaillant", - "model": self.boiler_status.device_name, + "model": self._name, } @property def device_state_attributes(self): """Return the state attributes.""" - if self.boiler_status is not None: + if self.available: return {"device_id": self._boiler_id, "error": self.boiler_status.is_error} return None @@ -392,7 +388,7 @@ def available(self) -> bool: @property def name(self): """Return the name of the entity.""" - return self.boiler_status.device_name if self.boiler_status else None + return self._name @property def boiler_status(self): @@ -408,7 +404,7 @@ def device_class(self): class MultimaticErrors(MultimaticEntity, BinarySensorEntity): """Check if there is any error message from system.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Init.""" super().__init__( coordinator, @@ -458,7 +454,7 @@ def name(self): class HolidayModeSensor(MultimaticEntity, BinarySensorEntity): """Binary sensor for holiday mode.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Init.""" super().__init__(coordinator, DOMAIN, "multimatic_holiday") @@ -484,11 +480,6 @@ def listening(self): """Return whether this entity is listening for system changes or not.""" return True - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return DEVICE_CLASS_POWER - @property def name(self): """Return the name of the entity.""" @@ -498,7 +489,7 @@ def name(self): class QuickModeSensor(MultimaticEntity, BinarySensorEntity): """Binary sensor for holiday mode.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Init.""" super().__init__(coordinator, DOMAIN, "multimatic_quick_mode") @@ -510,7 +501,7 @@ def is_on(self): @property def state_attributes(self): """Return the state attributes.""" - if self.coordinator.data.quick_mode: + if self.is_on: return {"quick_mode": self.coordinator.data.quick_mode.name} @property @@ -522,8 +513,3 @@ def listening(self): def name(self): """Return the name of the entity.""" return "Multimatic quick mode" - - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return DEVICE_CLASS_POWER diff --git a/custom_components/multimatic/climate.py b/custom_components/multimatic/climate.py index 2a1c85f..7fed0a9 100644 --- a/custom_components/multimatic/climate.py +++ b/custom_components/multimatic/climate.py @@ -1,8 +1,8 @@ """Interfaces with Multimatic climate.""" +from __future__ import annotations import abc import logging -from typing import Dict, List, Optional from pymultimatic.model import ( ActiveFunction, @@ -56,7 +56,7 @@ _LOGGER = logging.getLogger(__name__) -_FUNCTION_TO_HVAC_ACTION: Dict[ActiveFunction, str] = { +_FUNCTION_TO_HVAC_ACTION: dict[ActiveFunction, str] = { ActiveFunction.COOLING: CURRENT_HVAC_COOL, ActiveFunction.HEATING: CURRENT_HVAC_HEAT, ActiveFunction.STANDBY: CURRENT_HVAC_IDLE, @@ -160,32 +160,32 @@ def current_temperature(self): return self.component.temperature @property - def name(self) -> str: + def name(self) -> str | None: """Return the name of the entity.""" return self.component.name if self.component else None @property - def is_aux_heat(self) -> Optional[bool]: + def is_aux_heat(self) -> bool | None: """Return true if aux heater.""" return False @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return None @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return None @property - def swing_mode(self) -> Optional[str]: + def swing_mode(self) -> str | None: """Return the swing setting.""" return None @property - def swing_modes(self) -> Optional[List[str]]: + def swing_modes(self) -> list[str] | None: """Return the list of available swing modes.""" return None @@ -205,12 +205,12 @@ def turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return None @@ -218,7 +218,7 @@ def target_temperature_low(self) -> Optional[float]: class RoomClimate(MultimaticClimate): """Climate for a room.""" - _MULTIMATIC_TO_HA: Dict[Mode, list] = { + _MULTIMATIC_TO_HA: dict[Mode, list] = { OperatingModes.AUTO: [HVAC_MODE_AUTO, PRESET_COMFORT], OperatingModes.OFF: [HVAC_MODE_OFF, PRESET_NONE], OperatingModes.QUICK_VETO: [None, PRESET_QUICK_VETO], @@ -240,7 +240,7 @@ class RoomClimate(MultimaticClimate): def __init__( self, coordinator: MultimaticDataUpdateCoordinator, room: Room, zone: Zone - ): + ) -> None: """Initialize entity.""" super().__init__(coordinator, room.name, room) self._zone_id = zone.id @@ -261,7 +261,7 @@ def hvac_mode(self) -> str: return hvac_mode @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return self._supported_hvac @@ -302,7 +302,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: await self.coordinator.set_room_operating_mode(self, mode) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -310,7 +310,7 @@ def preset_mode(self) -> Optional[str]: return RoomClimate._MULTIMATIC_TO_HA[self.active_mode.current][1] @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. @@ -325,7 +325,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: await self.coordinator.set_room_operating_mode(self, mode) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. @@ -341,7 +341,7 @@ def hvac_action(self) -> Optional[str]: class ZoneClimate(MultimaticClimate): """Climate for a zone.""" - _MULTIMATIC_TO_HA: Dict[Mode, list] = { + _MULTIMATIC_TO_HA: dict[Mode, list] = { OperatingModes.AUTO: [HVAC_MODE_AUTO, PRESET_COMFORT], OperatingModes.DAY: [None, PRESET_DAY], OperatingModes.NIGHT: [None, PRESET_SLEEP], @@ -375,7 +375,9 @@ class ZoneClimate(MultimaticClimate): PRESET_COOLING_FOR_X_DAYS: QuickModes.COOLING_FOR_X_DAYS, } - def __init__(self, coordinator: MultimaticDataUpdateCoordinator, zone: Zone): + def __init__( + self, coordinator: MultimaticDataUpdateCoordinator, zone: Zone + ) -> None: """Initialize entity.""" super().__init__(coordinator, zone.id, zone) @@ -389,6 +391,13 @@ def __init__(self, coordinator: MultimaticDataUpdateCoordinator, zone: Zone): if not coordinator.data.ventilation: self._supported_hvac.remove(HVAC_MODE_FAN_ONLY) + self._name = zone.name + + @property + def name(self) -> str | None: + """Return the name of the entity.""" + return self._name + @property def hvac_mode(self): """Get the hvac mode based on multimatic mode.""" @@ -414,7 +423,7 @@ def hvac_mode(self): return hvac_mode @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return self._supported_hvac @@ -459,7 +468,7 @@ async def async_set_hvac_mode(self, hvac_mode): await self.coordinator.set_zone_operating_mode(self, mode) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. @@ -467,12 +476,12 @@ def hvac_action(self) -> Optional[str]: return _FUNCTION_TO_HVAC_ACTION.get(self.component.active_function) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return ZoneClimate._MULTIMATIC_TO_HA[self.active_mode.current][1] @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" if self.active_mode.current == OperatingModes.QUICK_VETO: return self._supported_presets + [PRESET_QUICK_VETO] diff --git a/custom_components/multimatic/config_flow.py b/custom_components/multimatic/config_flow.py index 9e0ff7d..cbe14f6 100644 --- a/custom_components/multimatic/config_flow.py +++ b/custom_components/multimatic/config_flow.py @@ -91,7 +91,7 @@ async def async_step_user(self, user_input=None): class MultimaticOptionsFlowHandler(config_entries.OptionsFlow): """Handle a option flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry): + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/custom_components/multimatic/const.py b/custom_components/multimatic/const.py index cf6973f..67dc22e 100644 --- a/custom_components/multimatic/const.py +++ b/custom_components/multimatic/const.py @@ -30,7 +30,6 @@ # configuration keys CONF_QUICK_VETO_DURATION = "quick_veto_duration" -CONF_SMARTPHONE_ID = "smartphoneid" CONF_SERIAL_NUMBER = "serial_number" # constants for states_attributes diff --git a/custom_components/multimatic/coordinator.py b/custom_components/multimatic/coordinator.py index 2e2a715..c259954 100644 --- a/custom_components/multimatic/coordinator.py +++ b/custom_components/multimatic/coordinator.py @@ -22,7 +22,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME -from homeassistant.helpers.aiohttp_client import async_create_clientsession +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( @@ -42,7 +42,7 @@ async def check_authentication(hass, username, password, serial): return await pymultimatic.systemmanager.SystemManager( username, password, - async_create_clientsession(hass), + async_get_clientsession(hass), DEFAULT_SMART_PHONE_ID, serial, ).login(True) @@ -68,9 +68,11 @@ def __init__(self, hass, entry: ConfigEntry): update_method=self._fetch_data, ) - session = async_create_clientsession(hass) self._manager = pymultimatic.systemmanager.SystemManager( - username, password, session, DEFAULT_SMART_PHONE_ID, serial + user=username, + password=password, + session=async_get_clientsession(hass), + serial=serial, ) self.fixed_serial = serial is not None @@ -108,7 +110,9 @@ async def _fetch_data(self): return system except ApiError as err: await self._log_error(err) - await self.authenticate() + if err.status < 500: + await self._manager.logout() + await self.authenticate() raise async def logout(self): @@ -122,13 +126,22 @@ async def logout(self): return True @staticmethod - async def _log_error(api_err): - _LOGGER.error( - "Error with multimatic API: %s, status: %s, response: %s", - api_err.message, - api_err.status, - api_err.response, - ) + async def _log_error(api_err, exec_info=True): + if api_err.status == 409: + _LOGGER.warning( + "Multimatic API: %s, status: %s, response: %s", + api_err.message, + api_err.status, + api_err.response, + ) + else: + _LOGGER.error( + "Error with multimatic API: %s, status: %s, response: %s", + api_err.message, + api_err.status, + api_err.response, + exc_info=exec_info, + ) def find_component(self, comp): """Find a component in the system with the given id, no IO is done.""" diff --git a/custom_components/multimatic/entities.py b/custom_components/multimatic/entities.py index 34d8560..b4f2c0a 100644 --- a/custom_components/multimatic/entities.py +++ b/custom_components/multimatic/entities.py @@ -1,7 +1,8 @@ """Common entities.""" +from __future__ import annotations + from abc import ABC import logging -from typing import Optional from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import slugify @@ -37,7 +38,7 @@ def __init__(self, coordinator: MultimaticDataUpdateCoordinator, domain, device_ self._remove_listener = None @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id diff --git a/custom_components/multimatic/fan.py b/custom_components/multimatic/fan.py index 657aa4f..4eef53e 100644 --- a/custom_components/multimatic/fan.py +++ b/custom_components/multimatic/fan.py @@ -1,7 +1,9 @@ """Interfaces with Multimatic fan.""" +from __future__ import annotations + import logging -from typing import Any, List, Optional +from typing import Any from pymultimatic.model import OperatingModes, QuickModes @@ -29,7 +31,7 @@ async def async_setup_entry(hass, entry, async_add_entities): class MultimaticFan(MultimaticEntity, FanEntity): """Representation of a multimatic fan.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Initialize entity.""" super().__init__( @@ -66,9 +68,9 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -98,12 +100,12 @@ def supported_features(self) -> int: return SUPPORT_PRESET_MODE @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite.""" return self.coordinator.data.get_active_mode_ventilation().current.name @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_SET_SPEED. diff --git a/custom_components/multimatic/manifest.json b/custom_components/multimatic/manifest.json index 498156a..4d3b4fa 100644 --- a/custom_components/multimatic/manifest.json +++ b/custom_components/multimatic/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://github.com/thomasgermain/vaillant-component", "issue_tracker": "https://github.com/thomasgermain/vaillant-component/issues", "requirements": [ - "pymultimatic==0.4.1" + "pymultimatic==0.4.2" ], "ssdp": [], "zeroconf": [], @@ -14,6 +14,6 @@ "codeowners": [ "@thomasgermain" ], - "version": "1.7.0b0", + "version": "1.7.0b1", "iot_class": "cloud_polling" } diff --git a/custom_components/multimatic/sensor.py b/custom_components/multimatic/sensor.py index e3d996d..ebef693 100644 --- a/custom_components/multimatic/sensor.py +++ b/custom_components/multimatic/sensor.py @@ -1,6 +1,8 @@ """Interfaces with multimatic sensors.""" + +from __future__ import annotations + import logging -from typing import Optional from pymultimatic.model import Report @@ -46,7 +48,7 @@ async def async_setup_entry(hass, entry, async_add_entities): class OutdoorTemperatureSensor(MultimaticEntity): """Outdoor temperature sensor.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Initialize entity.""" super().__init__(coordinator, DOMAIN, "outdoor_temperature") @@ -81,10 +83,17 @@ def device_class(self) -> str: class ReportSensor(MultimaticEntity): """Report sensor.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator, report: Report): + def __init__( + self, coordinator: MultimaticDataUpdateCoordinator, report: Report + ) -> None: """Init entity.""" MultimaticEntity.__init__(self, coordinator, DOMAIN, report.id) self._report_id = report.id + self._unit = report.unit + self._name = report.name + self._class = UNIT_TO_DEVICE_CLASS.get(report.unit, None) + self._device_name = report.device_name + self._device_id = report.device_id @property def report(self): @@ -102,9 +111,9 @@ def available(self): return super().available and self.report is not None @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" - return self.report.unit if self.report else None + return self._unit @property def device_info(self): @@ -113,20 +122,20 @@ def device_info(self): "identifiers": { ( DOMAIN, - self.report.device_id, + self._device_id, self.coordinator.data.info.serial_number, ) }, - "name": self.report.device_name, + "name": self._device_name, "manufacturer": "Vaillant", } @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" - return UNIT_TO_DEVICE_CLASS.get(self.report.unit, None) + return self._class @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" - return self.report.name if self.report else None + return self._name diff --git a/custom_components/multimatic/water_heater.py b/custom_components/multimatic/water_heater.py index 9776d7a..ac872d8 100644 --- a/custom_components/multimatic/water_heater.py +++ b/custom_components/multimatic/water_heater.py @@ -47,15 +47,16 @@ async def async_setup_entry(hass, entry, async_add_entities): class MultimaticWaterHeater(MultimaticEntity, WaterHeaterEntity): """Represent the multimatic water heater.""" - def __init__(self, coordinator: MultimaticDataUpdateCoordinator): + def __init__(self, coordinator: MultimaticDataUpdateCoordinator) -> None: """Initialize entity.""" super().__init__(coordinator, DOMAIN, coordinator.data.dhw.hotwater.id) self._operations = {mode.name: mode for mode in HotWater.MODES} + self._name = coordinator.data.dhw.hotwater.name @property def name(self) -> str: """Return the name of the entity.""" - return self.component.name if self.component else None + return self._name @property def listening(self):