From c3aaae49dab375f90b0b1db5943f054586b033b1 Mon Sep 17 00:00:00 2001 From: "Carlos J. Aliaga" Date: Mon, 5 Sep 2022 01:55:33 +0200 Subject: [PATCH] Allow setting zone temperature if device supports it (#4) * Bump aioaquarea dependency * Allow setting temperature of the zone if device supports it * Code clean up --- README.md | 7 +-- custom_components/aquarea/__init__.py | 2 + custom_components/aquarea/binary_sensor.py | 17 +++--- custom_components/aquarea/climate.py | 68 ++++++++++++++++------ custom_components/aquarea/config_flow.py | 3 +- custom_components/aquarea/coordinator.py | 3 +- custom_components/aquarea/manifest.json | 4 +- custom_components/aquarea/sensor.py | 7 ++- custom_components/aquarea/water_heater.py | 27 ++++++++- 9 files changed, 101 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 5f80ce4..c1bf7fc 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ This integration is currently in beta. Please report any issues you find and any **⚠️ Make sure to read the 'Remarks' and 'Warning' sections** ## Features -* Climate entity per device zone that allows you to control the operation mode and read the current temperature of the device/zone. +* Climate entity per device zone that allows you to control the operation mode, read the current temperature of the water in the device/zone and (if the zone supports it), change the target temperature. * Sensor entity for the outdoor temperature. -* Water heater entity for the hot water tank (if the device has one). +* Water heater entity for the hot water tank (if the device has one), that allows you to control the operation mode (enabled/disabled) and read the current temperature of the water in the tank. * Diagnostic sensor to indicate if the device has any problem (such not enough water flow). ## Features in the works @@ -22,8 +22,7 @@ This integration is currently in beta. Please report any issues you find and any * Weekly schedule. * Set the device in eco mode/quiet mode/holiday mode (if the device supports it). * Set the device in away mode (if the device supports it). -* Investigate if it's possible to set target temperature for the zone on devices that support it. - +* Additional sensors/switches for the device. ## Remarks Panasonic only allows one connection per account at the same time. This means that if you open the session from the Panasonic Confort Cloud app or the Panasonic Confort Cloud website, the session will be closed and you will be disconnected from Home Assistant. The integration will try to reconnect automatically, clossing the session from the app or the website. If you want to use the app or the website, you will have to temporarily disable the integration. diff --git a/custom_components/aquarea/__init__.py b/custom_components/aquarea/__init__.py index 8e61140..4fbd084 100644 --- a/custom_components/aquarea/__init__.py +++ b/custom_components/aquarea/__init__.py @@ -4,6 +4,7 @@ from typing import Any import aioaquarea + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant @@ -48,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id][CLIENT] = client try: + await client.login() # Get all the devices, we will filter the disabled ones later devices = await client.get_devices(include_long_id=True) diff --git a/custom_components/aquarea/binary_sensor.py b/custom_components/aquarea/binary_sensor.py index 8280834..9d169ef 100644 --- a/custom_components/aquarea/binary_sensor.py +++ b/custom_components/aquarea/binary_sensor.py @@ -1,7 +1,10 @@ +"""Diagnostics sensor that indicates if the Panasonic Aquarea Device is on error""" import logging -from homeassistant.components.binary_sensor import (BinarySensorDeviceClass, - BinarySensorEntity) +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory @@ -25,15 +28,13 @@ async def async_setup_entry( config_entry.entry_id ][DEVICES] - entities: list[StatusBinarySensor] = [] - - entities.extend([StatusBinarySensor(coordinator) for coordinator in data.values()]) - - async_add_entities(entities) + async_add_entities( + [StatusBinarySensor(coordinator) for coordinator in data.values()] + ) class StatusBinarySensor(AquareaBaseEntity, BinarySensorEntity): - """Representation of a Aquarea sensor.""" + """Representation of a Aquarea sensor that indicates if the device is on error""" _attr_has_entity_name = True diff --git a/custom_components/aquarea/climate.py b/custom_components/aquarea/climate.py index 24302b7..5803afa 100644 --- a/custom_components/aquarea/climate.py +++ b/custom_components/aquarea/climate.py @@ -1,15 +1,16 @@ +"""Climate entity to control a zone for a Panasonic Aquarea Device""" import logging from aioaquarea import DeviceAction, ExtendedOperationMode, UpdateOperationMode + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ClimateEntityFeature, HVACAction, HVACMode, ) -from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.config_entries import ConfigEntry -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -31,9 +32,7 @@ async def async_setup_entry( config_entry.entry_id ][DEVICES] - entities: list[HeatPumpClimate] = [] - - entities.extend( + async_add_entities( [ HeatPumpClimate(coordinator, zone_id) for coordinator in data.values() @@ -41,8 +40,6 @@ async def async_setup_entry( ] ) - async_add_entities(entities) - def get_hvac_mode_from_ext_op_mode(mode: ExtendedOperationMode) -> HVACMode: """Convert extended operation mode to HVAC mode.""" @@ -117,25 +114,62 @@ def _handle_coordinator_update(self) -> None: self._attr_hvac_mode = get_hvac_mode_from_ext_op_mode(device.mode) self._attr_hvac_action = get_hvac_action_from_ext_action(device.current_action) + self._attr_icon = ( + "mdi:hvac-off" if device.mode == ExtendedOperationMode.OFF else "mdi:hvac" + ) - # The device doesn't allow to set the temperature directly + zone = device.zones.get(self._zone_id) + self._attr_current_temperature = zone.temperature + + # If the device doesn't allow to set the temperature directly # So we set the max and min to the current temperature. - # This is a workaround to make the UI work - # We need to study if other devices allow to set the temperature and detect that - # programatelly to make this work for all devices. - # https://github.com/cjaliaga/aioaquarea/issues/7 - current_temperature = device.zones.get(self._zone_id).temperature - self._attr_current_temperature = current_temperature - self._attr_max_temp = current_temperature - self._attr_min_temp = current_temperature + # This is a workaround to make the UI work. + self._attr_max_temp = zone.temperature + self._attr_min_temp = zone.temperature + + if zone.supports_set_temperature and device.mode != ExtendedOperationMode.OFF: + self._attr_max_temp = ( + zone.cool_max + if device.mode + in (ExtendedOperationMode.COOL, ExtendedOperationMode.AUTO_COOL) + else zone.heat_max + ) + self._attr_min_temp = ( + zone.cool_min + if device.mode + in (ExtendedOperationMode.COOL, ExtendedOperationMode.AUTO_COOL) + else zone.heat_min + ) + self._attr_target_temperature_step = 1 super()._handle_coordinator_update() async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" + if hvac_mode not in self.hvac_modes: + raise ValueError(f"Unsupported HVAC mode: {hvac_mode}") + + _LOGGER.debug( + "Setting operation mode of %s to %s", + self.coordinator.device.device_id, + hvac_mode, + ) + await self.coordinator.device.set_mode( get_update_operation_mode_from_hvac_mode(hvac_mode), self._zone_id ) async def async_set_temperature(self, **kwargs) -> None: - """The device doesn't allow to set the temperature directly.""" + """Set new target temperature if supported by the zone""" + zone = self.coordinator.device.zones.get(self._zone_id) + temperature = kwargs.get(ATTR_TEMPERATURE, None) + + if temperature and zone.supports_set_temperature: + _LOGGER.debug( + "Setting temperature of device:zone == %s:%s to %s", + self.coordinator.device.device_id, + zone.name, + str(temperature), + ) + + await self.coordinator.device.set_temperature(temperature, zone.zone_id) diff --git a/custom_components/aquarea/config_flow.py b/custom_components/aquarea/config_flow.py index 36ce513..a655f44 100644 --- a/custom_components/aquarea/config_flow.py +++ b/custom_components/aquarea/config_flow.py @@ -1,13 +1,14 @@ """Config flow for Aquarea Smart Cloud integration.""" from __future__ import annotations -import logging from collections.abc import Mapping +import logging from typing import Any import aioaquarea import aiohttp import voluptuous as vol + from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult diff --git a/custom_components/aquarea/coordinator.py b/custom_components/aquarea/coordinator.py index 6f206fc..3f57677 100644 --- a/custom_components/aquarea/coordinator.py +++ b/custom_components/aquarea/coordinator.py @@ -1,10 +1,11 @@ """Coordinator for Aquarea.""" from __future__ import annotations -import logging from datetime import timedelta +import logging import aioaquarea + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_USERNAME from homeassistant.core import HomeAssistant diff --git a/custom_components/aquarea/manifest.json b/custom_components/aquarea/manifest.json index 5bf68ce..61848bd 100644 --- a/custom_components/aquarea/manifest.json +++ b/custom_components/aquarea/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://github.com/cjaliaga/home-assistant-aquarea", "issue_tracker": "https://github.com/cjaliaga/home-assistant-aquarea/issues", "requirements": [ - "aioaquarea==0.2.0" + "aioaquarea==0.3.0" ], "ssdp": [], "zeroconf": [], @@ -20,5 +20,5 @@ "@cjaliaga" ], "iot_class": "cloud_polling", - "version": "0.1.0a1" + "version": "0.1.0" } \ No newline at end of file diff --git a/custom_components/aquarea/sensor.py b/custom_components/aquarea/sensor.py index 0ebd6cb..307720e 100644 --- a/custom_components/aquarea/sensor.py +++ b/custom_components/aquarea/sensor.py @@ -1,7 +1,10 @@ import logging -from homeassistant.components.sensor import (SensorDeviceClass, SensorEntity, - SensorStateClass) +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorStateClass, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback diff --git a/custom_components/aquarea/water_heater.py b/custom_components/aquarea/water_heater.py index 6bb966b..a1815e5 100644 --- a/custom_components/aquarea/water_heater.py +++ b/custom_components/aquarea/water_heater.py @@ -1,15 +1,22 @@ """Defines the water heater entity to control the Aquarea water tank.""" from __future__ import annotations + import logging from aioaquarea.data import DeviceAction, OperationStatus + from homeassistant.components.water_heater import ( STATE_HEAT_PUMP, WaterHeaterEntity, WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PRECISION_WHOLE, STATE_OFF, TEMP_CELSIUS +from homeassistant.const import ( + ATTR_TEMPERATURE, + PRECISION_WHOLE, + STATE_OFF, + TEMP_CELSIUS, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -82,7 +89,13 @@ def _update_operation_state(self) -> None: if self.coordinator.device.tank.operation_status == OperationStatus.OFF: self._attr_state = STATE_OFF self._attr_current_operation = STATE_OFF + self._attr_icon = ( + "mdi:water-boiler-alert" + if self.coordinator.device.is_on_error + else "mdi:water-boiler-off" + ) else: + self._attr_icon = "mdi:water-boiler" self._attr_state = STATE_HEAT_PUMP self._attr_current_operation = ( HEATING @@ -98,10 +111,20 @@ def _update_temperature(self) -> None: async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - if temperature := kwargs.get("temperature"): + if temperature := kwargs.get(ATTR_TEMPERATURE): + _LOGGER.debug( + "Setting %s water tank temperature to %s", + self.coordinator.device.device_id, + str(temperature), + ) await self.coordinator.device.tank.set_target_temperature(int(temperature)) async def async_set_operation_mode(self, operation_mode): + _LOGGER.debug( + "Turning %s water tank %s", + self.coordinator.device.device_id, + operation_mode, + ) if operation_mode == HEATING: await self.coordinator.device.tank.turn_on() elif operation_mode == STATE_OFF: