diff --git a/custom_components/multimatic/binary_sensor.py b/custom_components/multimatic/binary_sensor.py index 3e5ff0d..bdac441 100644 --- a/custom_components/multimatic/binary_sensor.py +++ b/custom_components/multimatic/binary_sensor.py @@ -1,7 +1,9 @@ """Interfaces with Multimatic binary sensors.""" from __future__ import annotations +from collections.abc import Mapping import logging +from typing import Any from pymultimatic.model import Device, OperatingModes, QuickModes, Room, SettingModes @@ -10,8 +12,11 @@ BinarySensorDeviceClass, BinarySensorEntity, ) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import slugify from .const import ( @@ -32,9 +37,11 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the multimatic binary sensor platform.""" - sensors = [] + sensors: list[MultimaticEntity] = [] dhw_coo = get_coordinator(hass, DHW, entry.unique_id) if dhw_coo.data and dhw_coo.data.circulation: @@ -71,7 +78,6 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.info("Adding %s binary sensor entities", len(sensors)) async_add_entities(sensors) - return True class CirculationSensor(MultimaticEntity, BinarySensorEntity): @@ -82,7 +88,7 @@ def __init__(self, coordinator: MultimaticCoordinator) -> None: super().__init__(coordinator, DOMAIN, "dhw_circulation") @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" a_mode = self.active_mode return ( @@ -91,7 +97,7 @@ def is_on(self): ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( super().available @@ -115,7 +121,7 @@ def entity_category(self) -> EntityCategory | None: return EntityCategory.DIAGNOSTIC @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.RUNNING @@ -131,17 +137,17 @@ def __init__(self, coordinator: MultimaticCoordinator, room: Room) -> None: self._room_id = room.id @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self.room.window_open @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.room @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.WINDOW @@ -174,18 +180,18 @@ def __init__( self._sgtin = device.sgtin @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return device specific attributes.""" device = self.device - return { - "identifiers": {(MULTIMATIC, device.sgtin)}, - "name": device.name, - "manufacturer": "Vaillant", - "model": device.device_type, - } + return DeviceInfo( + identifiers={(MULTIMATIC, device.sgtin)}, + name=device.name, + manufacturer="Vaillant", + model=device.device_type, + ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return the state attributes.""" device = self.device return { @@ -195,7 +201,7 @@ def extra_state_attributes(self): } @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.device @@ -229,12 +235,12 @@ def __init__( self._room_id = room.id @property - def is_on(self): + def is_on(self) -> bool: """According to the doc, true means unlock, false lock.""" return not self.room.child_lock @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.room @@ -244,7 +250,7 @@ def room(self) -> Room: return self.coordinator.find_component(self._room_id) @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.LOCK @@ -262,12 +268,12 @@ def __init__(self, coordinator: MultimaticCoordinator, device: Device) -> None: super().__init__(coordinator, device, BinarySensorDeviceClass.BATTERY) @property - def is_on(self): + def is_on(self) -> bool: """According to the doc, true means normal, false low.""" return self.device.battery_low @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.BATTERY @@ -285,12 +291,12 @@ def __init__(self, coordinator: MultimaticCoordinator, device: Device) -> None: super().__init__(coordinator, device, BinarySensorDeviceClass.CONNECTIVITY) @property - def is_on(self): + def is_on(self) -> bool: """According to the doc, true means connected, false disconnected.""" return not self.device.radio_out_of_reach @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.CONNECTIVITY @@ -316,18 +322,19 @@ def __init__( self._gw_coo = gw_coo @property - def device_info(self): + def device_info(self) -> DeviceInfo | None: """Return device specific attributes.""" if self._detail_coo.data: detail = self._detail_coo.data - return { - "identifiers": {(MULTIMATIC, detail.serial_number)}, - "connections": {(CONNECTION_NETWORK_MAC, detail.ethernet_mac)}, - "name": self._gw_coo.data, - "manufacturer": "Vaillant", - "model": self._gw_coo.data, - "sw_version": detail.firmware_version, - } + return DeviceInfo( + identifiers={(MULTIMATIC, detail.serial_number)}, + connections={(CONNECTION_NETWORK_MAC, detail.ethernet_mac)}, + name=self._gw_coo.data, + manufacturer="Vaillant", + model=self._gw_coo.data, + sw_version=detail.firmware_version, + ) + return None class BoxUpdate(VRBoxEntity): @@ -348,7 +355,7 @@ def __init__( ) @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return not self.coordinator.data.is_up_to_date @@ -363,7 +370,7 @@ def entity_category(self) -> EntityCategory | None: return EntityCategory.DIAGNOSTIC @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.UPDATE @@ -381,17 +388,17 @@ def __init__( super().__init__(coord, detail_coo, gw_coo, "multimatic_system_online") @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self.coordinator.data.is_online @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return "Multimatic system Online" @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.CONNECTIVITY @@ -416,12 +423,12 @@ def __init__(self, coordinator: MultimaticCoordinator) -> None: self._boiler_id = slugify(self._name) @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self.boiler_status and self.boiler_status.is_error @property - def state_attributes(self): + def state_attributes(self) -> dict[str, Any] | None: """Return the state attributes.""" if self.boiler_status: return { @@ -429,19 +436,20 @@ def state_attributes(self): "title": self.boiler_status.title, "timestamp": self.boiler_status.timestamp, } + return None @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return device specific attributes.""" - return { - "identifiers": {(MULTIMATIC, self._boiler_id)}, - "name": self._name, - "manufacturer": "Vaillant", - "model": self._name, - } + return DeviceInfo( + identifiers={(MULTIMATIC, self._boiler_id)}, + name=self._name, + manufacturer="Vaillant", + model=self._name, + ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return the state attributes.""" if self.available: return {"device_id": self._boiler_id, "error": self.boiler_status.is_error} @@ -453,7 +461,7 @@ def available(self) -> bool: return super().available and self.boiler_status @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return self._name @@ -463,7 +471,7 @@ def boiler_status(self): return self.coordinator.data.boiler_status if self.coordinator.data else None @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.PROBLEM @@ -485,14 +493,14 @@ def __init__(self, coordinator: MultimaticCoordinator) -> None: ) @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" if self.coordinator.data.errors: return len(self.coordinator.data.errors) > 0 return False @property - def state_attributes(self): + def state_attributes(self) -> dict[str, Any] | None: """Return the state attributes.""" state_attributes = {} if self.coordinator.data.errors: @@ -511,12 +519,12 @@ def state_attributes(self): return state_attributes @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.PROBLEM @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return "Multimatic Errors" @@ -534,12 +542,12 @@ def __init__(self, coordinator: MultimaticCoordinator) -> None: super().__init__(coordinator, DOMAIN, "multimatic_holiday") @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self.coordinator.data is not None and self.coordinator.data.is_applied @property - def state_attributes(self): + def state_attributes(self) -> dict[str, Any] | None: """Return the state attributes.""" if self.is_on: return { @@ -547,9 +555,10 @@ def state_attributes(self): "end_date": self.coordinator.data.end_date.isoformat(), "temperature": self.coordinator.data.target, } + return None @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return "Multimatic holiday" @@ -564,7 +573,7 @@ def entity_category(self) -> EntityCategory | None: return EntityCategory.DIAGNOSTIC @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.OCCUPANCY @@ -577,12 +586,12 @@ def __init__(self, coordinator: MultimaticCoordinator) -> None: super().__init__(coordinator, DOMAIN, "multimatic_quick_mode") @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self.coordinator.data is not None @property - def state_attributes(self): + def state_attributes(self) -> dict[str, Any] | None: """Return the state attributes.""" attrs = {} if self.is_on: @@ -592,7 +601,7 @@ def state_attributes(self): return attrs @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return "Multimatic quick mode" @@ -607,6 +616,6 @@ def entity_category(self) -> EntityCategory | None: return EntityCategory.DIAGNOSTIC @property - def device_class(self) -> BinarySensorDeviceClass | str | None: + def device_class(self) -> BinarySensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return BinarySensorDeviceClass.RUNNING diff --git a/custom_components/multimatic/climate.py b/custom_components/multimatic/climate.py index a81c844..4b6b53a 100644 --- a/custom_components/multimatic/climate.py +++ b/custom_components/multimatic/climate.py @@ -18,21 +18,23 @@ ) from homeassistant.components.climate import ( - ClimateEntity, - ClimateEntityFeature, - HVACAction, - HVACMode, -) -from homeassistant.components.climate.const import ( DOMAIN, PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_NONE, PRESET_SLEEP, + ClimateEntity, + ClimateEntityFeature, + HVACAction, + HVACMode, ) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import SERVICES from .const import ( @@ -64,9 +66,11 @@ } -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the multimatic climate platform.""" - climates = [] + climates: list[MultimaticClimate] = [] zones_coo = get_coordinator(hass, ZONES, entry.unique_id) rooms_coo = get_coordinator(hass, ROOMS, entry.unique_id) ventilation_coo = get_coordinator(hass, VENTILATION, entry.unique_id) @@ -86,7 +90,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(climates) if len(climates) > 0: - platform = entity_platform.current_platform.get() + platform = entity_platform.async_get_current_platform() platform.async_register_entity_service( SERVICE_REMOVE_QUICK_VETO, SERVICES[SERVICE_REMOVE_QUICK_VETO]["schema"], @@ -98,8 +102,6 @@ async def async_setup_entry(hass, entry, async_add_entities): SERVICE_SET_QUICK_VETO, ) - return True - class MultimaticClimate(MultimaticEntity, ClimateEntity, abc.ABC): """Base class for climate.""" @@ -134,22 +136,22 @@ def component(self) -> Component: """Return the room or the zone.""" @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.component @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_CELSIUS + return UnitOfTemperature.CELSIUS @property - def target_temperature(self): + def target_temperature(self) -> float: """Return the temperature we try to reach.""" return self.active_mode.target @property - def current_temperature(self): + def current_temperature(self) -> float: """Return the current temperature.""" return self.component.temperature @@ -244,17 +246,17 @@ def __init__( self._zone_coo = zone_coo @property - def device_info(self): + def device_info(self) -> DeviceInfo | None: """Return device specific attributes.""" devices = self.component.devices if len(devices) == 1: # Can't link an entity to multiple devices - return { - "identifiers": {(MULTIMATIC, devices[0].sgtin)}, - "name": devices[0].name, - "manufacturer": "Vaillant", - "model": devices[0].device_type, - } - return {} + return DeviceInfo( + identifiers={(MULTIMATIC, devices[0].sgtin)}, + name=devices[0].name, + manufacturer="Vaillant", + model=devices[0].device_type, + ) + return None @property def component(self) -> Room: @@ -280,19 +282,19 @@ def hvac_modes(self) -> list[HVACMode]: return self._supported_hvac @property - def supported_features(self): + def supported_features(self) -> ClimateEntityFeature: """Return the list of supported features.""" return ( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE ) @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature.""" return Room.MIN_TARGET_TEMP @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature.""" return Room.MAX_TARGET_TEMP @@ -305,10 +307,10 @@ def zone(self): ) return None - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" await self.coordinator.api.set_room_target_temperature( - self, float(kwargs.get(ATTR_TEMPERATURE)) + self, kwargs.get(ATTR_TEMPERATURE) ) async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: @@ -433,7 +435,7 @@ def component(self) -> Zone: return self.coordinator.find_component(self._zone_id) @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Get the hvac mode based on multimatic mode.""" current_mode = self.active_mode.current hvac_mode = ZoneClimate._MULTIMATIC_TO_HA[current_mode][0] @@ -462,28 +464,28 @@ def hvac_modes(self) -> list[HVACMode]: return self._supported_hvac @property - def supported_features(self): + def supported_features(self) -> ClimateEntityFeature: """Return the list of supported features.""" return ( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE ) @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature.""" return Zone.MIN_TARGET_HEATING_TEMP @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature.""" return Zone.MAX_TARGET_TEMP @property - def target_temperature(self): + def target_temperature(self) -> float: """Return the temperature we try to reach.""" return self.active_mode.target - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) @@ -493,7 +495,7 @@ async def async_set_temperature(self, **kwargs): else: _LOGGER.debug("Nothing to do") - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" mode = ZoneClimate._HA_MODE_TO_MULTIMATIC[hvac_mode] await self.coordinator.api.set_zone_operating_mode(self, mode) diff --git a/custom_components/multimatic/config_flow.py b/custom_components/multimatic/config_flow.py index 4d5b84d..330657c 100644 --- a/custom_components/multimatic/config_flow.py +++ b/custom_components/multimatic/config_flow.py @@ -6,8 +6,10 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigEntry, OptionsFlow from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_create_clientsession import homeassistant.helpers.config_validation as cv @@ -60,11 +62,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Get the options flow for this handler.""" return MultimaticOptionsFlowHandler(config_entry) - async def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None) -> FlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -92,7 +94,7 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None): + async def async_step_init(self, user_input=None) -> FlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/custom_components/multimatic/fan.py b/custom_components/multimatic/fan.py index 05bc780..6f1b80b 100644 --- a/custom_components/multimatic/fan.py +++ b/custom_components/multimatic/fan.py @@ -9,7 +9,10 @@ from pymultimatic.model import OperatingModes, QuickModes from homeassistant.components.fan import DOMAIN, FanEntity, FanEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_LEVEL, VENTILATION from .coordinator import MultimaticCoordinator @@ -24,7 +27,9 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the multimatic fan platform.""" coordinator = get_coordinator(hass, VENTILATION, entry.unique_id) @@ -34,7 +39,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities([MultimaticFan(coordinator)]) _LOGGER.debug("Adding fan services") - platform = entity_platform.current_platform.get() + platform = entity_platform.async_get_current_platform() platform.async_register_entity_service( SERVICE_SET_VENTILATION_DAY_LEVEL, SERVICES[SERVICE_SET_VENTILATION_DAY_LEVEL]["schema"], @@ -85,7 +90,7 @@ async def async_turn_on( self, percentage: int | None = None, preset_mode: str | None = None, - **kwargs, + **kwargs: Any, ) -> None: """Turn on the fan.""" if preset_mode: @@ -94,19 +99,19 @@ async def async_turn_on( mode = OperatingModes.AUTO return await self.coordinator.api.set_fan_operating_mode(self, mode) - async def async_turn_off(self, **kwargs: Any): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn on the fan.""" return await self.coordinator.api.set_fan_operating_mode( self, OperatingModes.NIGHT ) @property - def is_on(self): + def is_on(self) -> bool: """Return true if the entity is on.""" return self.active_mode.current != OperatingModes.NIGHT @property - def supported_features(self) -> int: + def supported_features(self) -> FanEntityFeature: """Flag supported features.""" return FanEntityFeature.PRESET_MODE @@ -126,7 +131,7 @@ def preset_modes(self) -> list[str] | None: return self._preset_modes @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.component diff --git a/custom_components/multimatic/manifest.json b/custom_components/multimatic/manifest.json index fca2516..44ef60f 100644 --- a/custom_components/multimatic/manifest.json +++ b/custom_components/multimatic/manifest.json @@ -5,13 +5,13 @@ "documentation": "https://github.com/thomasgermain/vaillant-component", "issue_tracker": "https://github.com/thomasgermain/vaillant-component/issues", "requirements": [ - "pymultimatic==0.6.11" + "pymultimatic==0.6.12" ], "ssdp": [], "zeroconf": [], "homekit": {}, "dependencies": [], "codeowners": ["@thomasgermain"], - "version": "1.12.11", + "version": "1.12.12", "iot_class": "cloud_polling" } diff --git a/custom_components/multimatic/sensor.py b/custom_components/multimatic/sensor.py index 4a654c0..cceae16 100644 --- a/custom_components/multimatic/sensor.py +++ b/custom_components/multimatic/sensor.py @@ -12,8 +12,11 @@ SensorEntity, SensorStateClass, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import UnitOfEnergy, UnitOfTemperature -from homeassistant.helpers.entity import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from .const import EMF_REPORTS, OUTDOOR_TEMP, REPORTS @@ -31,9 +34,11 @@ } -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the multimatic sensors.""" - sensors = [] + sensors: list[MultimaticEntity] = [] outdoor_temp_coo = get_coordinator(hass, OUTDOOR_TEMP, entry.unique_id) reports_coo = get_coordinator(hass, REPORTS, entry.unique_id) emf_reports_coo = get_coordinator(hass, EMF_REPORTS, entry.unique_id) @@ -52,7 +57,6 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.info("Adding %s sensor entities", len(sensors)) async_add_entities(sensors) - return True class OutdoorTemperatureSensor(MultimaticEntity, SensorEntity): @@ -68,7 +72,7 @@ def native_value(self) -> StateType: return self.coordinator.data @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.coordinator.data is not None @@ -83,7 +87,7 @@ def name(self) -> str: return "Outdoor temperature" @property - def device_class(self) -> str: + def device_class(self) -> SensorDeviceClass: """Return the class of this device, from component DEVICE_CLASSES.""" return SensorDeviceClass.TEMPERATURE @@ -129,7 +133,7 @@ def native_value(self) -> StateType: return self.report.value @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.report is not None @@ -139,14 +143,14 @@ def native_unit_of_measurement(self) -> str | None: return self._unit @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return device specific attributes.""" - return { - "identifiers": {(DOMAIN, self._device_id)}, - "name": self._device_name, - "manufacturer": "Vaillant", - "model": self.report.device_id, - } + return DeviceInfo( + identifiers={(DOMAIN, self._device_id)}, + name=self._device_name, + manufacturer="Vaillant", + model=self.report.device_id, + ) @property def state_class(self) -> str | None: @@ -154,7 +158,7 @@ def state_class(self) -> str | None: return SensorStateClass.MEASUREMENT @property - def device_class(self) -> str | None: + def device_class(self) -> SensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return self._class @@ -192,12 +196,12 @@ def report(self): ) @property - def native_value(self): + def native_value(self) -> float: """Return the state of the entity.""" return self.report.value @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.report is not None @@ -207,17 +211,17 @@ def native_unit_of_measurement(self) -> str | None: return UnitOfEnergy.WATT_HOUR @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return device specific attributes.""" - return { - "identifiers": {(DOMAIN, self.report.device_id)}, - "name": self.report.device_name, - "manufacturer": "Vaillant", - "model": self.report.device_id, - } + return DeviceInfo( + identifiers={(DOMAIN, self.report.device_id)}, + name=self.report.device_name, + manufacturer="Vaillant", + model=self.report.device_id, + ) @property - def device_class(self) -> str | None: + def device_class(self) -> SensorDeviceClass | None: """Return the class of this device, from component DEVICE_CLASSES.""" return SensorDeviceClass.ENERGY diff --git a/custom_components/multimatic/utils.py b/custom_components/multimatic/utils.py index c2e2427..6875087 100644 --- a/custom_components/multimatic/utils.py +++ b/custom_components/multimatic/utils.py @@ -1,4 +1,6 @@ """Utility.""" +from __future__ import annotations + from datetime import datetime from pymultimatic.model import HolidayMode, QuickMode, QuickModes @@ -8,7 +10,7 @@ _DATE_FORMAT = "%Y-%m-%d" -def get_coordinator(hass, key: str, entry_id: str): +def get_coordinator(hass, key: str, entry_id: str | None): """Get coordinator from hass data.""" return hass.data[MULTIMATIC][entry_id][COORDINATORS][key] diff --git a/custom_components/multimatic/water_heater.py b/custom_components/multimatic/water_heater.py index f4ed644..4ab1ea6 100644 --- a/custom_components/multimatic/water_heater.py +++ b/custom_components/multimatic/water_heater.py @@ -1,14 +1,19 @@ """Interfaces with multimatic water heater.""" import logging +from typing import Any from pymultimatic.model import HotWater, OperatingModes, QuickModes from homeassistant.components.water_heater import ( DOMAIN, + UnitOfTemperature, WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DHW from .coordinator import MultimaticCoordinator @@ -33,7 +38,9 @@ ] -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up water_heater platform.""" entities = [] coordinator = get_coordinator(hass, DHW, entry.unique_id) @@ -42,7 +49,6 @@ async def async_setup_entry(hass, entry, async_add_entities): entities.append(MultimaticWaterHeater(coordinator)) async_add_entities(entities) - return True class MultimaticWaterHeater(MultimaticEntity, WaterHeaterEntity): @@ -70,22 +76,22 @@ def active_mode(self): return self.coordinator.api.get_active_mode(self.component) @property - def supported_features(self): + def supported_features(self) -> WaterHeaterEntityFeature: """Return the list of supported features. !! It could be misleading here, since when heater is not heating, - target temperature if fixed (35 °C) - The API doesn't allow to change + target temperature is fixed (5 °C) - The API doesn't allow to change this setting. It means if the user wants to change the target temperature, it will always be the target temperature when the heater is on function. See example below: - 1. Target temperature when heater is off is 35 (this is a fixed + 1. Target temperature when heater is off is 5 (this is a fixed setting) 2. Target temperature when heater is on is for instance 50 (this is a configurable setting) 3. While heater is off, user changes target_temperature to 45. It will actually change the target temperature from 50 to 45 - 4. While heater is off, user will still see 35 in UI + 4. While heater is off, user will still see 5 in UI (even if he changes to 45 before) 5. When heater will go on, user will see the target temperature he set at point 3 -> 45. @@ -94,63 +100,61 @@ def supported_features(self): is off, but it means the user will be able to change the target temperature only when the heater is ON (which seems odd to me) """ - if self.active_mode != QuickModes.HOLIDAY: - return SUPPORTED_FLAGS - return 0 + return SUPPORTED_FLAGS @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return super().available and self.component is not None @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_CELSIUS + return UnitOfTemperature.CELSIUS @property - def target_temperature(self): + def target_temperature(self) -> float: """Return the temperature we try to reach.""" return self.active_mode.target @property - def current_temperature(self): + def current_temperature(self) -> float: """Return the current temperature.""" return self.component.temperature @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature.""" return HotWater.MIN_TARGET_TEMP @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature.""" return HotWater.MAX_TARGET_TEMP @property - def current_operation(self): + def current_operation(self) -> str: """Return current operation ie. eco, electric, performance, ...""" return self.active_mode.current.name @property - def operation_list(self): + def operation_list(self) -> list[str]: """Return current operation ie. eco, electric, performance, ...""" if self.active_mode.current != QuickModes.HOLIDAY: return list(self._operations.keys()) return [] @property - def is_away_mode_on(self): + def is_away_mode_on(self) -> bool: """Return true if away mode is on.""" return self.active_mode.current in AWAY_MODES - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - target_temp = float(kwargs.get(ATTR_TEMPERATURE)) + target_temp = kwargs.get(ATTR_TEMPERATURE) await self.coordinator.api.set_hot_water_target_temperature(self, target_temp) - async def async_set_operation_mode(self, operation_mode): + async def async_set_operation_mode(self, operation_mode: str) -> None: """Set new target operation mode.""" if operation_mode in self._operations.keys(): mode = self._operations[operation_mode] @@ -158,13 +162,13 @@ async def async_set_operation_mode(self, operation_mode): else: _LOGGER.debug("Operation mode %s is unknown", operation_mode) - async def async_turn_away_mode_on(self): + async def async_turn_away_mode_on(self) -> None: """Turn away mode on.""" await self.coordinator.api.set_hot_water_operating_mode( self, OperatingModes.OFF ) - async def async_turn_away_mode_off(self): + async def async_turn_away_mode_off(self) -> None: """Turn away mode off.""" await self.coordinator.api.set_hot_water_operating_mode( self, OperatingModes.AUTO diff --git a/hacs.json b/hacs.json index 40bc1ac..f7a84a8 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { "name": "multimatic", "render_readme": true, - "homeassistant": "2022.11.0" + "homeassistant": "2023.1.0" }