From f0720c3f3306bcba3ff00485e06295738b17e9d3 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Wed, 25 Jan 2023 16:06:59 +0000 Subject: [PATCH 01/14] Add support for LIVISI climate devices --- homeassistant/components/livisi/__init__.py | 10 +- homeassistant/components/livisi/climate.py | 214 ++++++++++++++++++ .../components/livisi/config_flow.py | 33 ++- homeassistant/components/livisi/const.py | 7 + .../components/livisi/coordinator.py | 47 +++- homeassistant/components/livisi/manifest.json | 6 +- homeassistant/components/livisi/strings.json | 7 + 7 files changed, 315 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/livisi/climate.py diff --git a/homeassistant/components/livisi/__init__.py b/homeassistant/components/livisi/__init__.py index e71c6bca660cf2..82a44db6e53b7f 100644 --- a/homeassistant/components/livisi/__init__.py +++ b/homeassistant/components/livisi/__init__.py @@ -4,18 +4,18 @@ from typing import Final from aiohttp import ClientConnectorError -from aiolivisi import AioLivisi +from aiolivisi import AioLivisi, errors from homeassistant import core from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, device_registry as dr -from .const import DOMAIN, SWITCH_PLATFORM +from .const import CLIMATE_PLATFORM, DOMAIN, SWITCH_PLATFORM from .coordinator import LivisiDataUpdateCoordinator -PLATFORMS: Final = [SWITCH_PLATFORM] +PLATFORMS: Final = [SWITCH_PLATFORM, CLIMATE_PLATFORM] async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> bool: @@ -28,6 +28,8 @@ async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> boo await coordinator.async_set_all_rooms() except ClientConnectorError as exception: raise ConfigEntryNotReady from exception + except errors.TokenExpiredException as exception: + raise ConfigEntryAuthFailed() from exception hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator device_registry = dr.async_get(hass) diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py new file mode 100644 index 00000000000000..5726702b35a326 --- /dev/null +++ b/homeassistant/components/livisi/climate.py @@ -0,0 +1,214 @@ +"""Code to handle a Livisi Virtual Climate Control.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.climate import ( + ClimateEntity, + ClimateEntityFeature, + HVACMode, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import ( + DOMAIN, + LIVISI_REACHABILITY_CHANGE, + LIVISI_STATE_CHANGE, + LOGGER, + MAX_TEMPERATURE, + MIN_TEMPERATURE, + VRCC_DEVICE_TYPE, +) +from .coordinator import LivisiDataUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up climate device.""" + coordinator: LivisiDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + @callback + def handle_coordinator_update() -> None: + """Add climate device.""" + shc_devices: list[dict[str, Any]] = coordinator.data + entities: list[ClimateEntity] = [] + for device in shc_devices: + if ( + device["type"] == VRCC_DEVICE_TYPE + and device["id"] not in coordinator.devices + ): + livisi_climate: ClimateEntity = create_entity( + config_entry, device, coordinator + ) + LOGGER.debug("Include device type: %s", device.get("type")) + coordinator.devices.add(device["id"]) + entities.append(livisi_climate) + async_add_entities(entities) + + config_entry.async_on_unload( + coordinator.async_add_listener(handle_coordinator_update) + ) + + +def create_entity( + config_entry: ConfigEntry, + device: dict[str, Any], + coordinator: LivisiDataUpdateCoordinator, +) -> ClimateEntity: + """Create Climate Entity.""" + capabilities: list = device["capabilities"] + room_id: str = device["location"] + room_name: str = coordinator.rooms[room_id] + if coordinator.is_avatar: + temperature_capability = capabilities[0] + target_temperature_capability = capabilities[1] + else: + temperature_capability = capabilities[1] + target_temperature_capability = capabilities[0] + livisi_climate = LivisiClimate( + config_entry, + coordinator, + unique_id=device["id"], + manufacturer=device["manufacturer"], + device_type=device["type"], + target_temperature_capability=target_temperature_capability, + temperature_capability=temperature_capability, + humidity_capability=capabilities[2], + room=room_name, + ) + return livisi_climate + + +class LivisiClimate(CoordinatorEntity[LivisiDataUpdateCoordinator], ClimateEntity): + """Represents the Livisi Climate.""" + + def __init__( + self, + config_entry: ConfigEntry, + coordinator: LivisiDataUpdateCoordinator, + unique_id: str, + manufacturer: str, + device_type: str, + target_temperature_capability: str, + temperature_capability: str, + humidity_capability: str, + room: str, + ) -> None: + """Initialize the Livisi Climate.""" + self.config_entry = config_entry + self._attr_unique_id = unique_id + self._target_temperature_capability = target_temperature_capability + self._temperature_capability = temperature_capability + self._humidity_capability = humidity_capability + self.aio_livisi = coordinator.aiolivisi + self._attr_available = False + self._attr_hvac_modes = [HVACMode.HEAT] + self._attr_temperature_unit = TEMP_CELSIUS + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + self._attr_target_temperature_high = MAX_TEMPERATURE + self._attr_target_temperature_low = MIN_TEMPERATURE + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, unique_id)}, + manufacturer=manufacturer, + model=device_type, + name=room, + suggested_area=room, + via_device=(DOMAIN, config_entry.entry_id), + ) + super().__init__(coordinator) + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new target temperature.""" + response = await self.aio_livisi.async_vrcc_set_temperature( + self._target_temperature_capability, + kwargs.get(ATTR_TEMPERATURE), + self.coordinator.is_avatar, + ) + if response is None: + self._attr_available = False + raise HomeAssistantError(f"Failed to turn off {self._attr_name}") + + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Do nothing as LIVISI devices do not support changing the hvac mode.""" + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + target_temperature = await self.coordinator.async_get_vrcc_target_temperature( + self._target_temperature_capability + ) + temperature = await self.coordinator.async_get_vrcc_temperature( + self._temperature_capability + ) + humidity = await self.coordinator.async_get_vrcc_humidity( + self._humidity_capability + ) + if temperature is None: + self._attr_current_temperature = None + self._attr_available = False + else: + self._attr_target_temperature = target_temperature + self._attr_current_temperature = temperature + self._attr_current_humidity = humidity + self._attr_hvac_mode = HVACMode.HEAT + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{LIVISI_STATE_CHANGE}_{self._target_temperature_capability}", + self.update_target_temperature, + ) + ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{LIVISI_STATE_CHANGE}_{self._temperature_capability}", + self.update_temperature, + ) + ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{LIVISI_STATE_CHANGE}_{self._humidity_capability}", + self.update_humidity, + ) + ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{LIVISI_REACHABILITY_CHANGE}_{self.unique_id}", + self.update_reachability, + ) + ) + + @callback + def update_target_temperature(self, target_temperature: float) -> None: + """Update the target temperature of the climate device.""" + self._attr_target_temperature = target_temperature + self.async_write_ha_state() + + @callback + def update_temperature(self, current_temperature: float) -> None: + """Update the current temperature of the climate device.""" + self._attr_current_temperature = current_temperature + self.async_write_ha_state() + + @callback + def update_humidity(self, humidity: int) -> None: + """Update the humidity temperature of the climate device.""" + self._attr_current_humidity = humidity + self.async_write_ha_state() + + @callback + def update_reachability(self, is_reachable: bool) -> None: + """Update the reachability of the climate device.""" + self._attr_available = is_reachable + self.async_write_ha_state() diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index 16cccaacfd113f..0f5a29eb878c1a 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Livisi Home Assistant.""" from __future__ import annotations +from collections.abc import Mapping from contextlib import suppress from typing import Any @@ -9,15 +10,18 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client -from .const import CONF_HOST, CONF_PASSWORD, DOMAIN, LOGGER +from .const import AVATAR, AVATAR_VERSION, CONF_HOST, CONF_PASSWORD, DOMAIN, LOGGER class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Livisi Smart Home config flow.""" + reauth_entry: ConfigEntry | None = None + def __init__(self) -> None: """Create the configuration file.""" self.aio_livisi: AioLivisi = None @@ -56,6 +60,24 @@ async def async_step_user( step_id="user", data_schema=self.data_schema, errors=errors ) + async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + """Perform reauth upon an API authentication error.""" + self.reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + return await self.async_step_reauth_confirm(user_input) + + async def async_step_reauth_confirm( + self, user_input: Mapping[str, Any] + ) -> FlowResult: + """Dialog that informs the user that reauth is required.""" + if user_input is None: + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema({}), + ) + return await self.async_step_user() + async def _login(self, user_input: dict[str, str]) -> None: """Login into Livisi Smart Home.""" web_session = aiohttp_client.async_get_clientsession(self.hass) @@ -71,9 +93,18 @@ async def create_entity( self, user_input: dict[str, str], controller_info: dict[str, Any] ) -> FlowResult: """Create LIVISI entity.""" + if self.reauth_entry: + self.hass.config_entries.async_update_entry( + self.reauth_entry, data=controller_info + ) + await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) + return self.async_abort(reason="reauth_successful") + if (controller_data := controller_info.get("gateway")) is None: controller_data = controller_info controller_type = controller_data["controllerType"] + if controller_type == AVATAR: + controller_type = AVATAR_VERSION LOGGER.debug( "Integrating SHC %s with serial number: %s", controller_type, diff --git a/homeassistant/components/livisi/const.py b/homeassistant/components/livisi/const.py index e6abc5118deab0..38ac76ef123f55 100644 --- a/homeassistant/components/livisi/const.py +++ b/homeassistant/components/livisi/const.py @@ -7,6 +7,8 @@ CONF_HOST = "host" CONF_PASSWORD: Final = "password" +AVATAR = "Avatar" +AVATAR_VERSION = "2.0" AVATAR_PORT: Final = 9090 CLASSIC_PORT: Final = 8080 DEVICE_POLLING_DELAY: Final = 60 @@ -14,5 +16,10 @@ LIVISI_REACHABILITY_CHANGE: Final = "livisi_reachability_change" SWITCH_PLATFORM: Final = "switch" +CLIMATE_PLATFORM: Final = "climate" PSS_DEVICE_TYPE: Final = "PSS" +VRCC_DEVICE_TYPE: Final = "VRCC" + +MAX_TEMPERATURE: Final = 30.0 +MIN_TEMPERATURE: Final = 6.0 diff --git a/homeassistant/components/livisi/coordinator.py b/homeassistant/components/livisi/coordinator.py index 47a612274ac3f1..f2f8e98588e19c 100644 --- a/homeassistant/components/livisi/coordinator.py +++ b/homeassistant/components/livisi/coordinator.py @@ -13,7 +13,9 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( + AVATAR, AVATAR_PORT, + AVATAR_VERSION, CLASSIC_PORT, CONF_HOST, CONF_PASSWORD, @@ -69,14 +71,16 @@ async def async_setup(self) -> None: livisi_connection_data=livisi_connection_data ) controller_data = await self.aiolivisi.async_get_controller() - if controller_data["controllerType"] == "Avatar": + if controller_data["controllerType"] == AVATAR: self.port = AVATAR_PORT self.is_avatar = True + controller_type = AVATAR_VERSION else: self.port = CLASSIC_PORT self.is_avatar = False + controller_type = controller_data["controllerType"] self.serial_number = controller_data["serialNumber"] - self.controller_type = controller_data["controllerType"] + self.controller_type = controller_type async def async_get_devices(self) -> list[dict[str, Any]]: """Set the discovered devices list.""" @@ -92,6 +96,39 @@ async def async_get_pss_state(self, capability: str) -> bool | None: on_state = response["onState"] return on_state["value"] + async def async_get_vrcc_target_temperature(self, capability: str) -> float | None: + """Get the target temperature of the climate device.""" + response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + capability[1:] + ) + if response is None: + return None + if self.is_avatar: + temperature = response["setpointTemperature"] + else: + temperature = response["pointTemperature"] + return temperature["value"] + + async def async_get_vrcc_temperature(self, capability: str) -> float | None: + """Get the temperature of the climate device.""" + response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + capability[1:] + ) + if response is None: + return None + temperature = response["temperature"] + return temperature["value"] + + async def async_get_vrcc_humidity(self, capability: str) -> int | None: + """Get the humidity of the climate device.""" + response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + capability[1:] + ) + if response is None: + return None + humidity = response["humidity"] + return humidity["value"] + async def async_set_all_rooms(self) -> None: """Set the room list.""" response: list[dict[str, Any]] = await self.aiolivisi.async_get_all_rooms() @@ -108,6 +145,12 @@ def on_data(self, event_data: LivisiEvent) -> None: f"{LIVISI_STATE_CHANGE}_{event_data.source}", event_data.onState, ) + if event_data.vrccData is not None: + async_dispatcher_send( + self.hass, + f"{LIVISI_STATE_CHANGE}_{event_data.source}", + event_data.vrccData, + ) if event_data.isReachable is not None: async_dispatcher_send( self.hass, diff --git a/homeassistant/components/livisi/manifest.json b/homeassistant/components/livisi/manifest.json index 6cdebeb307f647..a38e50d9c5ade5 100644 --- a/homeassistant/components/livisi/manifest.json +++ b/homeassistant/components/livisi/manifest.json @@ -1,9 +1,11 @@ { "domain": "livisi", "name": "LIVISI Smart Home", - "codeowners": ["@StefanIacobLivisi"], + "codeowners": [ + "@StefanIacobLivisi" + ], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/livisi", "iot_class": "local_polling", "requirements": ["aiolivisi==0.0.16"] -} +} \ No newline at end of file diff --git a/homeassistant/components/livisi/strings.json b/homeassistant/components/livisi/strings.json index 260ef07234ba88..4703fe81545d7f 100644 --- a/homeassistant/components/livisi/strings.json +++ b/homeassistant/components/livisi/strings.json @@ -7,12 +7,19 @@ "host": "[%key:common::config_flow::data::ip%]", "password": "[%key:common::config_flow::data::password%]" } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The LIVISI integration needs to re-authenticate your account" } }, "error": { "wrong_password": "The password is incorrect.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_ip_address": "The IP address is incorrect or the SHC cannot be reached locally." + }, + "abort": { + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } From db95f458670ceec6f109dd8a36a3499d855252a8 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Thu, 26 Jan 2023 13:34:03 +0000 Subject: [PATCH 02/14] Remove the reauthentication logic --- homeassistant/components/livisi/__init__.py | 6 ++-- homeassistant/components/livisi/climate.py | 6 ++-- .../components/livisi/config_flow.py | 29 ------------------- homeassistant/components/livisi/strings.json | 7 ----- 4 files changed, 5 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/livisi/__init__.py b/homeassistant/components/livisi/__init__.py index 82a44db6e53b7f..563b038164fe80 100644 --- a/homeassistant/components/livisi/__init__.py +++ b/homeassistant/components/livisi/__init__.py @@ -4,12 +4,12 @@ from typing import Final from aiohttp import ClientConnectorError -from aiolivisi import AioLivisi, errors +from aiolivisi import AioLivisi from homeassistant import core from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, device_registry as dr from .const import CLIMATE_PLATFORM, DOMAIN, SWITCH_PLATFORM @@ -28,8 +28,6 @@ async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> boo await coordinator.async_set_all_rooms() except ClientConnectorError as exception: raise ConfigEntryNotReady from exception - except errors.TokenExpiredException as exception: - raise ConfigEntryAuthFailed() from exception hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator device_registry = dr.async_get(hass) diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py index 5726702b35a326..b73bdbd8f069c3 100644 --- a/homeassistant/components/livisi/climate.py +++ b/homeassistant/components/livisi/climate.py @@ -9,7 +9,7 @@ HVACMode, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -113,7 +113,8 @@ def __init__( self.aio_livisi = coordinator.aiolivisi self._attr_available = False self._attr_hvac_modes = [HVACMode.HEAT] - self._attr_temperature_unit = TEMP_CELSIUS + self._attr_hvac_mode = HVACMode.HEAT + self._attr_temperature_unit = UnitOfTemperature.CELSIUS self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE self._attr_target_temperature_high = MAX_TEMPERATURE self._attr_target_temperature_low = MIN_TEMPERATURE @@ -159,7 +160,6 @@ async def async_added_to_hass(self) -> None: self._attr_target_temperature = target_temperature self._attr_current_temperature = temperature self._attr_current_humidity = humidity - self._attr_hvac_mode = HVACMode.HEAT self.async_on_remove( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index 0f5a29eb878c1a..c8468841359913 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -1,7 +1,6 @@ """Config flow for Livisi Home Assistant.""" from __future__ import annotations -from collections.abc import Mapping from contextlib import suppress from typing import Any @@ -10,7 +9,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client @@ -20,8 +18,6 @@ class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Livisi Smart Home config flow.""" - reauth_entry: ConfigEntry | None = None - def __init__(self) -> None: """Create the configuration file.""" self.aio_livisi: AioLivisi = None @@ -60,24 +56,6 @@ async def async_step_user( step_id="user", data_schema=self.data_schema, errors=errors ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: - """Perform reauth upon an API authentication error.""" - self.reauth_entry = self.hass.config_entries.async_get_entry( - self.context["entry_id"] - ) - return await self.async_step_reauth_confirm(user_input) - - async def async_step_reauth_confirm( - self, user_input: Mapping[str, Any] - ) -> FlowResult: - """Dialog that informs the user that reauth is required.""" - if user_input is None: - return self.async_show_form( - step_id="reauth_confirm", - data_schema=vol.Schema({}), - ) - return await self.async_step_user() - async def _login(self, user_input: dict[str, str]) -> None: """Login into Livisi Smart Home.""" web_session = aiohttp_client.async_get_clientsession(self.hass) @@ -93,13 +71,6 @@ async def create_entity( self, user_input: dict[str, str], controller_info: dict[str, Any] ) -> FlowResult: """Create LIVISI entity.""" - if self.reauth_entry: - self.hass.config_entries.async_update_entry( - self.reauth_entry, data=controller_info - ) - await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) - return self.async_abort(reason="reauth_successful") - if (controller_data := controller_info.get("gateway")) is None: controller_data = controller_info controller_type = controller_data["controllerType"] diff --git a/homeassistant/components/livisi/strings.json b/homeassistant/components/livisi/strings.json index 4703fe81545d7f..260ef07234ba88 100644 --- a/homeassistant/components/livisi/strings.json +++ b/homeassistant/components/livisi/strings.json @@ -7,19 +7,12 @@ "host": "[%key:common::config_flow::data::ip%]", "password": "[%key:common::config_flow::data::password%]" } - }, - "reauth_confirm": { - "title": "[%key:common::config_flow::title::reauth%]", - "description": "The LIVISI integration needs to re-authenticate your account" } }, "error": { "wrong_password": "The password is incorrect.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_ip_address": "The IP address is incorrect or the SHC cannot be reached locally." - }, - "abort": { - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } From aa28a77386df655debdf54fd3397556d4aa072b9 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Wed, 25 Jan 2023 16:06:59 +0000 Subject: [PATCH 03/14] Add support for LIVISI climate devices --- homeassistant/components/livisi/__init__.py | 6 ++-- .../components/livisi/config_flow.py | 29 +++++++++++++++++++ homeassistant/components/livisi/strings.json | 7 +++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/livisi/__init__.py b/homeassistant/components/livisi/__init__.py index 563b038164fe80..82a44db6e53b7f 100644 --- a/homeassistant/components/livisi/__init__.py +++ b/homeassistant/components/livisi/__init__.py @@ -4,12 +4,12 @@ from typing import Final from aiohttp import ClientConnectorError -from aiolivisi import AioLivisi +from aiolivisi import AioLivisi, errors from homeassistant import core from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, device_registry as dr from .const import CLIMATE_PLATFORM, DOMAIN, SWITCH_PLATFORM @@ -28,6 +28,8 @@ async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> boo await coordinator.async_set_all_rooms() except ClientConnectorError as exception: raise ConfigEntryNotReady from exception + except errors.TokenExpiredException as exception: + raise ConfigEntryAuthFailed() from exception hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator device_registry = dr.async_get(hass) diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index c8468841359913..0f5a29eb878c1a 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Livisi Home Assistant.""" from __future__ import annotations +from collections.abc import Mapping from contextlib import suppress from typing import Any @@ -9,6 +10,7 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client @@ -18,6 +20,8 @@ class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Livisi Smart Home config flow.""" + reauth_entry: ConfigEntry | None = None + def __init__(self) -> None: """Create the configuration file.""" self.aio_livisi: AioLivisi = None @@ -56,6 +60,24 @@ async def async_step_user( step_id="user", data_schema=self.data_schema, errors=errors ) + async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + """Perform reauth upon an API authentication error.""" + self.reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + return await self.async_step_reauth_confirm(user_input) + + async def async_step_reauth_confirm( + self, user_input: Mapping[str, Any] + ) -> FlowResult: + """Dialog that informs the user that reauth is required.""" + if user_input is None: + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema({}), + ) + return await self.async_step_user() + async def _login(self, user_input: dict[str, str]) -> None: """Login into Livisi Smart Home.""" web_session = aiohttp_client.async_get_clientsession(self.hass) @@ -71,6 +93,13 @@ async def create_entity( self, user_input: dict[str, str], controller_info: dict[str, Any] ) -> FlowResult: """Create LIVISI entity.""" + if self.reauth_entry: + self.hass.config_entries.async_update_entry( + self.reauth_entry, data=controller_info + ) + await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) + return self.async_abort(reason="reauth_successful") + if (controller_data := controller_info.get("gateway")) is None: controller_data = controller_info controller_type = controller_data["controllerType"] diff --git a/homeassistant/components/livisi/strings.json b/homeassistant/components/livisi/strings.json index 260ef07234ba88..4703fe81545d7f 100644 --- a/homeassistant/components/livisi/strings.json +++ b/homeassistant/components/livisi/strings.json @@ -7,12 +7,19 @@ "host": "[%key:common::config_flow::data::ip%]", "password": "[%key:common::config_flow::data::password%]" } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The LIVISI integration needs to re-authenticate your account" } }, "error": { "wrong_password": "The password is incorrect.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_ip_address": "The IP address is incorrect or the SHC cannot be reached locally." + }, + "abort": { + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } From 352901521b145cd959a02401f3162867b3af01ec Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Thu, 26 Jan 2023 14:00:38 +0000 Subject: [PATCH 04/14] Remove the reauthentication support --- homeassistant/components/livisi/__init__.py | 6 ++-- .../components/livisi/config_flow.py | 29 ------------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/livisi/__init__.py b/homeassistant/components/livisi/__init__.py index 82a44db6e53b7f..563b038164fe80 100644 --- a/homeassistant/components/livisi/__init__.py +++ b/homeassistant/components/livisi/__init__.py @@ -4,12 +4,12 @@ from typing import Final from aiohttp import ClientConnectorError -from aiolivisi import AioLivisi, errors +from aiolivisi import AioLivisi from homeassistant import core from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, device_registry as dr from .const import CLIMATE_PLATFORM, DOMAIN, SWITCH_PLATFORM @@ -28,8 +28,6 @@ async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> boo await coordinator.async_set_all_rooms() except ClientConnectorError as exception: raise ConfigEntryNotReady from exception - except errors.TokenExpiredException as exception: - raise ConfigEntryAuthFailed() from exception hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator device_registry = dr.async_get(hass) diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index 0f5a29eb878c1a..c8468841359913 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -1,7 +1,6 @@ """Config flow for Livisi Home Assistant.""" from __future__ import annotations -from collections.abc import Mapping from contextlib import suppress from typing import Any @@ -10,7 +9,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client @@ -20,8 +18,6 @@ class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Livisi Smart Home config flow.""" - reauth_entry: ConfigEntry | None = None - def __init__(self) -> None: """Create the configuration file.""" self.aio_livisi: AioLivisi = None @@ -60,24 +56,6 @@ async def async_step_user( step_id="user", data_schema=self.data_schema, errors=errors ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: - """Perform reauth upon an API authentication error.""" - self.reauth_entry = self.hass.config_entries.async_get_entry( - self.context["entry_id"] - ) - return await self.async_step_reauth_confirm(user_input) - - async def async_step_reauth_confirm( - self, user_input: Mapping[str, Any] - ) -> FlowResult: - """Dialog that informs the user that reauth is required.""" - if user_input is None: - return self.async_show_form( - step_id="reauth_confirm", - data_schema=vol.Schema({}), - ) - return await self.async_step_user() - async def _login(self, user_input: dict[str, str]) -> None: """Login into Livisi Smart Home.""" web_session = aiohttp_client.async_get_clientsession(self.hass) @@ -93,13 +71,6 @@ async def create_entity( self, user_input: dict[str, str], controller_info: dict[str, Any] ) -> FlowResult: """Create LIVISI entity.""" - if self.reauth_entry: - self.hass.config_entries.async_update_entry( - self.reauth_entry, data=controller_info - ) - await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) - return self.async_abort(reason="reauth_successful") - if (controller_data := controller_info.get("gateway")) is None: controller_data = controller_info controller_type = controller_data["controllerType"] From 8da16587e91ba978b67177b0d2d25de85082799a Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Thu, 16 Feb 2023 15:21:57 +0000 Subject: [PATCH 05/14] Code review follow-up --- .coveragerc | 1 + homeassistant/components/livisi/strings.json | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.coveragerc b/.coveragerc index 7429966011a742..00e8f0e5c21b2e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -639,6 +639,7 @@ omit = homeassistant/components/linode/* homeassistant/components/linux_battery/sensor.py homeassistant/components/lirc/* + homeassistant/components/livisi/climate.py homeassistant/components/llamalab_automate/notify.py homeassistant/components/logi_circle/__init__.py homeassistant/components/logi_circle/camera.py diff --git a/homeassistant/components/livisi/strings.json b/homeassistant/components/livisi/strings.json index 4703fe81545d7f..260ef07234ba88 100644 --- a/homeassistant/components/livisi/strings.json +++ b/homeassistant/components/livisi/strings.json @@ -7,19 +7,12 @@ "host": "[%key:common::config_flow::data::ip%]", "password": "[%key:common::config_flow::data::password%]" } - }, - "reauth_confirm": { - "title": "[%key:common::config_flow::title::reauth%]", - "description": "The LIVISI integration needs to re-authenticate your account" } }, "error": { "wrong_password": "The password is incorrect.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_ip_address": "The IP address is incorrect or the SHC cannot be reached locally." - }, - "abort": { - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } From a3c5a603c3113ade659cd22806fd65fe39fdebe6 Mon Sep 17 00:00:00 2001 From: StefanIacobLivisi <109964424+StefanIacobLivisi@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:30:03 +0200 Subject: [PATCH 06/14] Update homeassistant/components/livisi/manifest.json Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/components/livisi/manifest.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/livisi/manifest.json b/homeassistant/components/livisi/manifest.json index a38e50d9c5ade5..8b8e8ce7872d5c 100644 --- a/homeassistant/components/livisi/manifest.json +++ b/homeassistant/components/livisi/manifest.json @@ -1,9 +1,7 @@ { "domain": "livisi", "name": "LIVISI Smart Home", - "codeowners": [ - "@StefanIacobLivisi" - ], + "codeowners": ["@StefanIacobLivisi"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/livisi", "iot_class": "local_polling", From 750f9fb78d8d206754702be57312174667515b5b Mon Sep 17 00:00:00 2001 From: StefanIacobLivisi <109964424+StefanIacobLivisi@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:30:14 +0200 Subject: [PATCH 07/14] Update homeassistant/components/livisi/manifest.json Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/components/livisi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/livisi/manifest.json b/homeassistant/components/livisi/manifest.json index 8b8e8ce7872d5c..6cdebeb307f647 100644 --- a/homeassistant/components/livisi/manifest.json +++ b/homeassistant/components/livisi/manifest.json @@ -6,4 +6,4 @@ "documentation": "https://www.home-assistant.io/integrations/livisi", "iot_class": "local_polling", "requirements": ["aiolivisi==0.0.16"] -} \ No newline at end of file +} From c89ed383b7e9ca3376b799192ca232e28d8645c1 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Fri, 17 Feb 2023 16:40:50 +0000 Subject: [PATCH 08/14] Code review follow-up --- homeassistant/components/livisi/__init__.py | 5 +++-- homeassistant/components/livisi/const.py | 3 --- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/livisi/__init__.py b/homeassistant/components/livisi/__init__.py index 563b038164fe80..b8d8fdbfb09999 100644 --- a/homeassistant/components/livisi/__init__.py +++ b/homeassistant/components/livisi/__init__.py @@ -8,14 +8,15 @@ from homeassistant import core from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, device_registry as dr -from .const import CLIMATE_PLATFORM, DOMAIN, SWITCH_PLATFORM +from .const import DOMAIN from .coordinator import LivisiDataUpdateCoordinator -PLATFORMS: Final = [SWITCH_PLATFORM, CLIMATE_PLATFORM] +PLATFORMS: Final = [Platform.CLIMATE, Platform.SWITCH] async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/livisi/const.py b/homeassistant/components/livisi/const.py index 38ac76ef123f55..5c56918428fcbb 100644 --- a/homeassistant/components/livisi/const.py +++ b/homeassistant/components/livisi/const.py @@ -15,9 +15,6 @@ LIVISI_STATE_CHANGE: Final = "livisi_state_change" LIVISI_REACHABILITY_CHANGE: Final = "livisi_reachability_change" -SWITCH_PLATFORM: Final = "switch" -CLIMATE_PLATFORM: Final = "climate" - PSS_DEVICE_TYPE: Final = "PSS" VRCC_DEVICE_TYPE: Final = "VRCC" From d76e5d7dd773a20253acbd058f1d9d1bea9ba944 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Fri, 24 Feb 2023 13:13:46 +0000 Subject: [PATCH 09/14] Code Review Follow-up --- homeassistant/components/livisi/climate.py | 17 +++++++---------- homeassistant/components/livisi/config_flow.py | 4 +--- homeassistant/components/livisi/const.py | 1 - homeassistant/components/livisi/coordinator.py | 4 +--- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py index b73bdbd8f069c3..f42afdee1155c8 100644 --- a/homeassistant/components/livisi/climate.py +++ b/homeassistant/components/livisi/climate.py @@ -1,8 +1,11 @@ """Code to handle a Livisi Virtual Climate Control.""" from __future__ import annotations +from collections.abc import Mapping from typing import Any +from aiolivisi.const import CAPABILITY_MAP + from homeassistant.components.climate import ( ClimateEntity, ClimateEntityFeature, @@ -66,24 +69,18 @@ def create_entity( coordinator: LivisiDataUpdateCoordinator, ) -> ClimateEntity: """Create Climate Entity.""" - capabilities: list = device["capabilities"] + capabilities: Mapping[str, Any] = device[CAPABILITY_MAP] room_id: str = device["location"] room_name: str = coordinator.rooms[room_id] - if coordinator.is_avatar: - temperature_capability = capabilities[0] - target_temperature_capability = capabilities[1] - else: - temperature_capability = capabilities[1] - target_temperature_capability = capabilities[0] livisi_climate = LivisiClimate( config_entry, coordinator, unique_id=device["id"], manufacturer=device["manufacturer"], device_type=device["type"], - target_temperature_capability=target_temperature_capability, - temperature_capability=temperature_capability, - humidity_capability=capabilities[2], + target_temperature_capability=capabilities["RoomSetpoint"], + temperature_capability=capabilities["RoomTemperature"], + humidity_capability=capabilities["RoomHumidity"], room=room_name, ) return livisi_climate diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index c8468841359913..16cccaacfd113f 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client -from .const import AVATAR, AVATAR_VERSION, CONF_HOST, CONF_PASSWORD, DOMAIN, LOGGER +from .const import CONF_HOST, CONF_PASSWORD, DOMAIN, LOGGER class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -74,8 +74,6 @@ async def create_entity( if (controller_data := controller_info.get("gateway")) is None: controller_data = controller_info controller_type = controller_data["controllerType"] - if controller_type == AVATAR: - controller_type = AVATAR_VERSION LOGGER.debug( "Integrating SHC %s with serial number: %s", controller_type, diff --git a/homeassistant/components/livisi/const.py b/homeassistant/components/livisi/const.py index 5c56918428fcbb..684510cf7e327d 100644 --- a/homeassistant/components/livisi/const.py +++ b/homeassistant/components/livisi/const.py @@ -8,7 +8,6 @@ CONF_HOST = "host" CONF_PASSWORD: Final = "password" AVATAR = "Avatar" -AVATAR_VERSION = "2.0" AVATAR_PORT: Final = 9090 CLASSIC_PORT: Final = 8080 DEVICE_POLLING_DELAY: Final = 60 diff --git a/homeassistant/components/livisi/coordinator.py b/homeassistant/components/livisi/coordinator.py index f2f8e98588e19c..940f9a5ae18fdd 100644 --- a/homeassistant/components/livisi/coordinator.py +++ b/homeassistant/components/livisi/coordinator.py @@ -15,7 +15,6 @@ from .const import ( AVATAR, AVATAR_PORT, - AVATAR_VERSION, CLASSIC_PORT, CONF_HOST, CONF_PASSWORD, @@ -74,11 +73,10 @@ async def async_setup(self) -> None: if controller_data["controllerType"] == AVATAR: self.port = AVATAR_PORT self.is_avatar = True - controller_type = AVATAR_VERSION else: self.port = CLASSIC_PORT self.is_avatar = False - controller_type = controller_data["controllerType"] + controller_type = controller_data["controllerType"] self.serial_number = controller_data["serialNumber"] self.controller_type = controller_type From 7650baa708335008463948bad4d0f982755d54d0 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Fri, 24 Feb 2023 15:17:04 +0000 Subject: [PATCH 10/14] Code Review Follow-up --- .../components/livisi/coordinator.py | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/livisi/coordinator.py b/homeassistant/components/livisi/coordinator.py index 940f9a5ae18fdd..3978c094ba6d13 100644 --- a/homeassistant/components/livisi/coordinator.py +++ b/homeassistant/components/livisi/coordinator.py @@ -70,15 +70,14 @@ async def async_setup(self) -> None: livisi_connection_data=livisi_connection_data ) controller_data = await self.aiolivisi.async_get_controller() - if controller_data["controllerType"] == AVATAR: + if (controller_type := controller_data["controllerType"]) == AVATAR: self.port = AVATAR_PORT self.is_avatar = True else: self.port = CLASSIC_PORT self.is_avatar = False - controller_type = controller_data["controllerType"] - self.serial_number = controller_data["serialNumber"] self.controller_type = controller_type + self.serial_number = controller_data["serialNumber"] async def async_get_devices(self) -> list[dict[str, Any]]: """Set the discovered devices list.""" @@ -99,33 +98,23 @@ async def async_get_vrcc_target_temperature(self, capability: str) -> float | No response: dict[str, Any] = await self.aiolivisi.async_get_device_state( capability[1:] ) - if response is None: - return None if self.is_avatar: - temperature = response["setpointTemperature"] - else: - temperature = response["pointTemperature"] - return temperature["value"] + return response["setpointTemperature"]["value"] + return response["pointTemperature"]["value"] async def async_get_vrcc_temperature(self, capability: str) -> float | None: """Get the temperature of the climate device.""" response: dict[str, Any] = await self.aiolivisi.async_get_device_state( capability[1:] ) - if response is None: - return None - temperature = response["temperature"] - return temperature["value"] + return response["temperature"]["value"] async def async_get_vrcc_humidity(self, capability: str) -> int | None: """Get the humidity of the climate device.""" response: dict[str, Any] = await self.aiolivisi.async_get_device_state( capability[1:] ) - if response is None: - return None - humidity = response["humidity"] - return humidity["value"] + return response["humidity"]["value"] async def async_set_all_rooms(self) -> None: """Set the room list.""" From 2a1a5e518814c43225e457a58ed43744b0514136 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Mon, 27 Feb 2023 07:50:41 +0000 Subject: [PATCH 11/14] Code review follow-up --- homeassistant/components/livisi/coordinator.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/livisi/coordinator.py b/homeassistant/components/livisi/coordinator.py index 3978c094ba6d13..e6c29f7151e443 100644 --- a/homeassistant/components/livisi/coordinator.py +++ b/homeassistant/components/livisi/coordinator.py @@ -85,7 +85,7 @@ async def async_get_devices(self) -> list[dict[str, Any]]: async def async_get_pss_state(self, capability: str) -> bool | None: """Set the PSS state.""" - response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + response: dict[str, Any] | None = await self.aiolivisi.async_get_device_state( capability[1:] ) if response is None: @@ -95,25 +95,31 @@ async def async_get_pss_state(self, capability: str) -> bool | None: async def async_get_vrcc_target_temperature(self, capability: str) -> float | None: """Get the target temperature of the climate device.""" - response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + response: dict[str, Any] | None = await self.aiolivisi.async_get_device_state( capability[1:] ) + if response is None: + return None if self.is_avatar: return response["setpointTemperature"]["value"] return response["pointTemperature"]["value"] async def async_get_vrcc_temperature(self, capability: str) -> float | None: """Get the temperature of the climate device.""" - response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + response: dict[str, Any] | None = await self.aiolivisi.async_get_device_state( capability[1:] ) + if response is None: + return None return response["temperature"]["value"] async def async_get_vrcc_humidity(self, capability: str) -> int | None: """Get the humidity of the climate device.""" - response: dict[str, Any] = await self.aiolivisi.async_get_device_state( + response: dict[str, Any] | None = await self.aiolivisi.async_get_device_state( capability[1:] ) + if response is None: + return None return response["humidity"]["value"] async def async_set_all_rooms(self) -> None: From 820cc462a5c6123d1d5eadffd50ebb301288da04 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Mon, 27 Feb 2023 08:06:57 +0000 Subject: [PATCH 12/14] Code review follow-up --- homeassistant/components/livisi/climate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py index f42afdee1155c8..6bb92896ac5d08 100644 --- a/homeassistant/components/livisi/climate.py +++ b/homeassistant/components/livisi/climate.py @@ -138,6 +138,9 @@ async def async_set_temperature(self, **kwargs: Any) -> None: def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Do nothing as LIVISI devices do not support changing the hvac mode.""" + raise HomeAssistantError( + "This feature is not supported with the LIVISI climate devices" + ) async def async_added_to_hass(self) -> None: """Register callbacks.""" From 2b4bacacefbe55dbf5184ead175405b96de276db Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Mon, 27 Feb 2023 08:39:44 +0000 Subject: [PATCH 13/14] Code review follow-up --- .coveragerc | 3 +++ homeassistant/components/livisi/climate.py | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.coveragerc b/.coveragerc index 00e8f0e5c21b2e..a06f4fa92d3010 100644 --- a/.coveragerc +++ b/.coveragerc @@ -639,7 +639,10 @@ omit = homeassistant/components/linode/* homeassistant/components/linux_battery/sensor.py homeassistant/components/lirc/* + homeassistant/components/livisi/__init__.py homeassistant/components/livisi/climate.py + homeassistant/components/livisi/coordinator.py + homeassistant/components/livisi/switch.py homeassistant/components/llamalab_automate/notify.py homeassistant/components/logi_circle/__init__.py homeassistant/components/logi_circle/camera.py diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py index 6bb92896ac5d08..2f7c5d63dc2aff 100644 --- a/homeassistant/components/livisi/climate.py +++ b/homeassistant/components/livisi/climate.py @@ -109,12 +109,12 @@ def __init__( self._humidity_capability = humidity_capability self.aio_livisi = coordinator.aiolivisi self._attr_available = False - self._attr_hvac_modes = [HVACMode.HEAT] - self._attr_hvac_mode = HVACMode.HEAT - self._attr_temperature_unit = UnitOfTemperature.CELSIUS - self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE - self._attr_target_temperature_high = MAX_TEMPERATURE - self._attr_target_temperature_low = MIN_TEMPERATURE + _attr_hvac_modes = [HVACMode.HEAT] + _attr_hvac_mode = HVACMode.HEAT + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + _attr_target_temperature_high = MAX_TEMPERATURE + _attr_target_temperature_low = MIN_TEMPERATURE self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, unique_id)}, manufacturer=manufacturer, From 3b463f0b2fcfbd5846860aadd593d606a2e58d86 Mon Sep 17 00:00:00 2001 From: "stefan.iacob" Date: Mon, 27 Feb 2023 08:46:31 +0000 Subject: [PATCH 14/14] Code review follow-up --- homeassistant/components/livisi/climate.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py index 2f7c5d63dc2aff..d0bdbe64bf752f 100644 --- a/homeassistant/components/livisi/climate.py +++ b/homeassistant/components/livisi/climate.py @@ -89,6 +89,13 @@ def create_entity( class LivisiClimate(CoordinatorEntity[LivisiDataUpdateCoordinator], ClimateEntity): """Represents the Livisi Climate.""" + _attr_hvac_modes = [HVACMode.HEAT] + _attr_hvac_mode = HVACMode.HEAT + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + _attr_target_temperature_high = MAX_TEMPERATURE + _attr_target_temperature_low = MIN_TEMPERATURE + def __init__( self, config_entry: ConfigEntry, @@ -109,12 +116,6 @@ def __init__( self._humidity_capability = humidity_capability self.aio_livisi = coordinator.aiolivisi self._attr_available = False - _attr_hvac_modes = [HVACMode.HEAT] - _attr_hvac_mode = HVACMode.HEAT - _attr_temperature_unit = UnitOfTemperature.CELSIUS - _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE - _attr_target_temperature_high = MAX_TEMPERATURE - _attr_target_temperature_low = MIN_TEMPERATURE self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, unique_id)}, manufacturer=manufacturer,