From 6e30ea6c0251adc5b751d92b7d16891cb694a28f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20D=C3=B6rner?= Date: Thu, 23 Feb 2023 21:32:24 +0100 Subject: [PATCH] Added energy efficiency sensor, fixed pre-commit warnings --- .pre-commit-config.yaml | 6 +- CHANGELOG.md | 4 ++ custom_components/mypyllant/__init__.py | 54 +++++++++++++--- custom_components/mypyllant/config_flow.py | 2 +- custom_components/mypyllant/sensor.py | 69 ++++++++++++++++++--- custom_components/mypyllant/water_heater.py | 2 +- setup.cfg | 2 +- 7 files changed, 117 insertions(+), 22 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2904ba1..57caf9e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: args: - --safe - --quiet - files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$ + files: ^((homeassistant|script|tests|custom_components)/.+)?[^/]+\.py$ - repo: https://github.com/codespell-project/codespell rev: v2.2.2 hooks: @@ -27,7 +27,7 @@ repos: - id: flake8 additional_dependencies: - pydocstyle==5.0.2 - files: ^(homeassistant|script|tests)/.+\.py$ + files: ^(homeassistant|script|tests|custom_components)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.7.4 hooks: @@ -36,7 +36,7 @@ repos: - --quiet - --format=custom - --configfile=tests/bandit.yaml - files: ^(homeassistant|script|tests)/.+\.py$ + files: ^(homeassistant|script|tests|custom_components)/.+\.py$ - repo: https://github.com/pre-commit/mirrors-isort rev: v5.10.1 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be86aa..c0cf4b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.0.16 + +* Added energy efficiency sensor + ## v0.0.15 * Fallback for None values in sensors diff --git a/custom_components/mypyllant/__init__.py b/custom_components/mypyllant/__init__.py index 9f8bd6e..ff176cb 100644 --- a/custom_components/mypyllant/__init__.py +++ b/custom_components/mypyllant/__init__.py @@ -35,16 +35,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: system_coordinator = SystemCoordinator( hass, api, entry, timedelta(seconds=update_interval) ) - _LOGGER.debug(f"Refreshing SystemCoordinator") + _LOGGER.debug("Refreshing SystemCoordinator") await system_coordinator.async_refresh() - data_coordinator = HistoricalDataCoordinator(hass, api, entry, timedelta(hours=1)) - _LOGGER.debug(f"Refreshing HistoricalDataCoordinator") - await data_coordinator.async_refresh() + + hourly_data_coordinator = HourlyDataCoordinator( + hass, api, entry, timedelta(hours=1) + ) + _LOGGER.debug("Refreshing HourlyDataCoordinator") + await hourly_data_coordinator.async_refresh() + + daily_data_coordinator = DailyDataCoordinator(hass, api, entry, timedelta(hours=1)) + _LOGGER.debug("Refreshing DailyDataCoordinator") + await daily_data_coordinator.async_refresh() hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { "system_coordinator": system_coordinator, - "data_coordinator": data_coordinator, + "hourly_data_coordinator": hourly_data_coordinator, + "daily_data_coordinator": daily_data_coordinator, } await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -57,7 +65,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if unload_ok: await hass.data[DOMAIN][entry.entry_id][ "system_coordinator" - ].api.session.close() + ].api.aiohttp_session.close() + await hass.data[DOMAIN][entry.entry_id][ + "hourly_data_coordinator" + ].api.aiohttp_session.close() + await hass.data[DOMAIN][entry.entry_id][ + "daily_data_coordinator" + ].api.aiohttp_session.close() hass.data[DOMAIN].pop(entry.entry_id) return unload_ok @@ -110,7 +124,7 @@ class SystemCoordinator(MyPyllantCoordinator): data: list[System] async def _async_update_data(self) -> list[System]: - _LOGGER.debug(f"Starting async update data for SystemCoordinator") + _LOGGER.debug("Starting async update data for SystemCoordinator") await self._refresh_session() data = [ s @@ -119,11 +133,11 @@ async def _async_update_data(self) -> list[System]: return data -class HistoricalDataCoordinator(MyPyllantCoordinator): +class HourlyDataCoordinator(MyPyllantCoordinator): data: list[list[DeviceData]] async def _async_update_data(self) -> list[list[DeviceData]]: - _LOGGER.debug(f"Starting async update data for HistoricalDataCoordinator") + _LOGGER.debug("Starting async update data for HourlyDataCoordinator") await self._refresh_session() data = [] start = datetime.now().replace(microsecond=0, second=0, minute=0, hour=0) @@ -138,3 +152,25 @@ async def _async_update_data(self) -> list[list[DeviceData]]: ) data.append([da async for da in device_data]) return data + + +class DailyDataCoordinator(MyPyllantCoordinator): + data: dict[str, list[DeviceData]] + + async def _async_update_data(self) -> dict[str, list[DeviceData]]: + _LOGGER.debug("Starting async update data for DailyDataCoordinator") + await self._refresh_session() + data = {} + start = datetime.now().replace(microsecond=0, second=0, minute=0, hour=0) + end = start + timedelta(days=1) + _LOGGER.debug(f"Getting data from {start} to {end}") + async for system in await self.hass.async_add_executor_job( + self.api.get_systems + ): + data[system.id] = [] + async for device in self.api.get_devices_by_system(system): + device_data = self.api.get_data_by_device( + device, DeviceDataBucketResolution.DAY, start, end + ) + data[system.id] += [da async for da in device_data] + return data diff --git a/custom_components/mypyllant/config_flow.py b/custom_components/mypyllant/config_flow.py index ebd4944..3e0385a 100644 --- a/custom_components/mypyllant/config_flow.py +++ b/custom_components/mypyllant/config_flow.py @@ -33,7 +33,7 @@ async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: api = MyPyllantAPI(data["username"], data["password"]) try: await api.login() - except: + except Exception: raise AuthenticationFailed finally: await api.aiohttp_session.close() diff --git a/custom_components/mypyllant/sensor.py b/custom_components/mypyllant/sensor.py index 6c47c8c..7176f82 100644 --- a/custom_components/mypyllant/sensor.py +++ b/custom_components/mypyllant/sensor.py @@ -25,7 +25,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HistoricalDataCoordinator, SystemCoordinator +from . import DailyDataCoordinator, HourlyDataCoordinator, SystemCoordinator from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -52,8 +52,11 @@ async def async_setup_entry( system_coordinator: SystemCoordinator = hass.data[DOMAIN][config.entry_id][ "system_coordinator" ] - data_coordinator: HistoricalDataCoordinator = hass.data[DOMAIN][config.entry_id][ - "data_coordinator" + hourly_data_coordinator: HourlyDataCoordinator = hass.data[DOMAIN][config.entry_id][ + "hourly_data_coordinator" + ] + daily_data_coordinator: DailyDataCoordinator = hass.data[DOMAIN][config.entry_id][ + "daily_data_coordinator" ] sensors: list[SensorEntity] = [] for index, system in enumerate(system_coordinator.data): @@ -116,9 +119,12 @@ async def async_setup_entry( index, dhw_index, system_coordinator ) ) - for device_index, device_data_list in enumerate(data_coordinator.data): + for device_index, device_data_list in enumerate(hourly_data_coordinator.data): for da_index, _ in enumerate(device_data_list): - sensors.append(DataSensor(device_index, da_index, data_coordinator)) + sensors.append(DataSensor(device_index, da_index, hourly_data_coordinator)) + + for system_id in daily_data_coordinator.data.keys(): + sensors.append(EfficiencySensor(system_id, daily_data_coordinator)) async_add_entities(sensors) @@ -562,11 +568,11 @@ def unique_id(self) -> str: class DataSensor(CoordinatorEntity, SensorEntity): - coordinator: HistoricalDataCoordinator + coordinator: HourlyDataCoordinator _attr_state_class = SensorStateClass.TOTAL def __init__( - self, device_index: int, da_index: int, coordinator: HistoricalDataCoordinator + self, device_index: int, da_index: int, coordinator: HourlyDataCoordinator ) -> None: super().__init__(coordinator) self.device_index = device_index @@ -616,3 +622,52 @@ def device_info(self): @property def native_value(self): return self.data_bucket.value if self.data_bucket else None + + +class EfficiencySensor(CoordinatorEntity, SensorEntity): + coordinator: DailyDataCoordinator + _attr_state_class = SensorStateClass.MEASUREMENT + _attr_name = "Heating Energy Efficiency" + + def __init__(self, system_id: str, coordinator: DailyDataCoordinator) -> None: + super().__init__(coordinator) + self.system_id = system_id + + @property + def device_data_list(self) -> list[DeviceData]: + return self.coordinator.data[self.system_id] + + @property + def energy_consumed(self) -> float: + return sum( + [ + v.data[-1].value + for v in self.device_data_list + if v.energy_type == "CONSUMED_ELECTRICAL_ENERGY" and len(v.data) + ] + ) + + @property + def heat_energy_generated(self) -> float: + return sum( + [ + v.data[-1].value + for v in self.device_data_list + if v.energy_type == "HEAT_GENERATED" and len(v.data) + ] + ) + + @property + def unique_id(self) -> str: + return f"{DOMAIN}_heating_energy_efficiency_{self.system_id}" + + @property + def device_info(self): + return {"identifiers": {(DOMAIN, f"system{self.system_id}")}} + + @property + def native_value(self) -> float | None: + if self.energy_consumed: + return round(self.heat_energy_generated / self.energy_consumed, 1) + else: + return None diff --git a/custom_components/mypyllant/water_heater.py b/custom_components/mypyllant/water_heater.py index bd38e8d..74f1b00 100644 --- a/custom_components/mypyllant/water_heater.py +++ b/custom_components/mypyllant/water_heater.py @@ -1,5 +1,5 @@ import logging -from typing import Any, List +from typing import Any from myPyllant.models import ( DHWCurrentSpecialFunction, diff --git a/setup.cfg b/setup.cfg index 683e609..9049d93 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,7 +49,7 @@ not_skip = __init__.py force_sort_within_sections = true sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER default_section = THIRDPARTY -known_first_party = homeassistant,tests +known_first_party = homeassistant,tests,custom_components forced_separate = tests combine_as_imports = true