From 6de9c7b386f1330ca28f27ce7c6c0aeaaa90cb7a Mon Sep 17 00:00:00 2001 From: magnuselden Date: Wed, 11 Sep 2024 14:29:36 +0200 Subject: [PATCH] black format --- custom_components/peaqhvac/__init__.py | 30 +- custom_components/peaqhvac/climate.py | 20 +- custom_components/peaqhvac/config_flow.py | 62 ++-- .../configflow/config_flow_schemas.py | 21 +- .../peaqhvac/extensionmethods.py | 1 + custom_components/peaqhvac/number.py | 6 +- custom_components/peaqhvac/sensor.py | 112 +++--- custom_components/peaqhvac/sensors/const.py | 4 +- .../peaqhvac/sensors/min_maxsensor.py | 6 +- .../peaqhvac/sensors/money_data_sensor.py | 19 +- .../peaqhvac/sensors/offsetsensor.py | 34 +- .../peaqhvac/sensors/peaqsensor.py | 4 +- .../peaqhvac/sensors/simple_money_sensor.py | 9 +- .../peaqhvac/sensors/simple_sensor.py | 21 +- .../peaqhvac/sensors/trendsensor.py | 12 +- .../peaqhvac/service/hub/average.py | 8 +- custom_components/peaqhvac/service/hub/hub.py | 37 +- .../peaqhvac/service/hub/hub_factory.py | 35 +- .../peaqhvac/service/hub/hubsensors.py | 23 +- .../peaqhvac/service/hub/state_changes.py | 33 +- .../peaqhvac/service/hub/target_temp.py | 32 +- .../peaqhvac/service/hub/weather_prognosis.py | 61 ++-- .../peaqhvac/service/hvac/const.py | 3 +- .../house_heater/house_heater_coordinator.py | 77 +++-- .../hvac/house_heater/house_heater_helpers.py | 54 ++- .../house_heater/models/calculated_offset.py | 1 + .../house_heater/models/offset_adjustments.py | 1 + .../hvac/house_heater/temperature_helper.py | 16 +- .../service/hvac/house_ventilation.py | 102 ++++-- .../peaqhvac/service/hvac/hvacfactory.py | 7 +- .../peaqhvac/service/hvac/hvactypes/ivt.py | 7 +- .../peaqhvac/service/hvac/hvactypes/nibe.py | 24 +- .../service/hvac/hvactypes/thermia.py | 9 +- .../service/hvac/interfaces/iheater.py | 2 + .../service/hvac/interfaces/ihvactype.py | 48 ++- .../service/hvac/offset/offset_cache.py | 11 +- .../service/hvac/offset/offset_coordinator.py | 75 ++-- .../hvac/offset/offset_coordinator_factory.py | 13 +- .../hvac/offset/offset_coordinator_peaqev.py | 31 +- .../offset/offset_coordinator_standalone.py | 9 +- .../service/hvac/offset/offset_utils.py | 25 +- .../service/hvac/offset/peakfinder.py | 30 +- .../service/hvac/water_heater/const.py | 1 - .../hvac/water_heater/cycle_waterboost.py | 25 +- .../service/hvac/water_heater/models/group.py | 2 +- .../models/next_water_boost_model.py | 125 ++++--- .../water_heater/models/waterbooster_model.py | 3 +- .../water_heater/water_heater_coordinator.py | 57 +++- .../water_heater/water_heater_next_start.py | 115 +++++-- .../peaqhvac/service/models/config_model.py | 10 +- .../service/models/enums/group_type.py | 1 + .../service/models/enums/hvac_presets.py | 3 +- .../peaqhvac/service/models/ihvac_model.py | 1 - .../peaqhvac/service/models/offset_model.py | 33 +- .../service/models/offsets_exportmodel.py | 9 +- .../service/models/prognosis_export_model.py | 2 +- .../peaqhvac/service/models/weather_object.py | 3 +- .../service/observer/event_property.py | 6 +- .../service/observer/iobserver_coordinator.py | 26 +- .../service/observer/models/observer_model.py | 1 + .../service/observer/observer_coordinator.py | 8 +- custom_components/peaqhvac/services.py | 4 - custom_components/peaqhvac/switch.py | 7 +- .../peaqhvac/test/test_offsets.py | 226 ++++++++++-- .../peaqhvac/test/test_peakfinder.py | 60 +++- .../peaqhvac/test/test_temperature_helpers.py | 21 +- .../test/test_water_heater_next_start.py | 189 ++++++++++- .../test/test_water_heater_next_start_new.py | 321 +++++++++++++++--- 68 files changed, 1778 insertions(+), 616 deletions(-) diff --git a/custom_components/peaqhvac/__init__.py b/custom_components/peaqhvac/__init__.py index d4802a0f..ea075f09 100644 --- a/custom_components/peaqhvac/__init__.py +++ b/custom_components/peaqhvac/__init__.py @@ -1,4 +1,5 @@ """The peaqhvac integration.""" + from __future__ import annotations import logging @@ -16,9 +17,11 @@ _LOGGER = logging.getLogger(__name__) + async def async_get_existing_param(conf, parameter: str, default_val: any): return conf.options.get(parameter, conf.data.get(parameter, default_val)) + async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config.entry_id] = config.data @@ -31,12 +34,26 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: huboptions.outdoor_tempsensors = huboptions.set_sensors_from_string( await async_get_existing_param(config, "outdoor_tempsensors", "") ) - huboptions.heating_options.outdoor_temp_stop_heating = await async_get_existing_param(config, "outdoor_temp_stop_heating", 15) - huboptions.heating_options.non_hours_water_boost = await async_get_existing_param(config, "non_hours_water_boost",[]) - huboptions.heating_options.demand_hours_water_boost = await async_get_existing_param(config, "demand_hours_water_boost",[]) + huboptions.heating_options.outdoor_temp_stop_heating = ( + await async_get_existing_param(config, "outdoor_temp_stop_heating", 15) + ) + huboptions.heating_options.non_hours_water_boost = await async_get_existing_param( + config, "non_hours_water_boost", [] + ) + huboptions.heating_options.demand_hours_water_boost = ( + await async_get_existing_param(config, "demand_hours_water_boost", []) + ) - huboptions.heating_options.low_degree_minutes = int((await async_get_existing_param(config, "low_degree_minutes","-600")).replace(" ", "")) - huboptions.heating_options.very_cold_temp = int((await async_get_existing_param(config, "very_cold_temp","-12")).replace(" ", "")) + huboptions.heating_options.low_degree_minutes = int( + (await async_get_existing_param(config, "low_degree_minutes", "-600")).replace( + " ", "" + ) + ) + huboptions.heating_options.very_cold_temp = int( + (await async_get_existing_param(config, "very_cold_temp", "-12")).replace( + " ", "" + ) + ) huboptions.systemid = config.data["systemid"] huboptions.hvacbrand = huboptions.set_hvacbrand( @@ -47,7 +64,7 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: hass.data[DOMAIN]["hub"] = hub - #await hub.async_setup() + # await hub.async_setup() await async_setup_services(hass, hub) await hass.config_entries.async_forward_entry_setups(config, PLATFORMS) @@ -71,6 +88,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok + async def async_update_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Reload Peaqev component when options changed.""" _LOGGER.debug("Reloading PeaqHvac component") diff --git a/custom_components/peaqhvac/climate.py b/custom_components/peaqhvac/climate.py index ce6790b5..1295a306 100644 --- a/custom_components/peaqhvac/climate.py +++ b/custom_components/peaqhvac/climate.py @@ -2,17 +2,23 @@ from datetime import timedelta from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import (PRESET_AWAY, PRESET_ECO, - PRESET_NONE, - HVACAction, HVACMode, ClimateEntityFeature) +from homeassistant.components.climate.const import ( + PRESET_AWAY, + PRESET_ECO, + PRESET_NONE, + HVACAction, + HVACMode, + ClimateEntityFeature, +) from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.restore_state import RestoreEntity import custom_components.peaqhvac.extensionmethods as ex from custom_components.peaqhvac.const import CLIMATE_SENSOR, DOMAIN -from custom_components.peaqhvac.service.models.enums.hvacmode import \ - HvacMode as HvacModeInternal +from custom_components.peaqhvac.service.models.enums.hvacmode import ( + HvacMode as HvacModeInternal, +) _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=10) @@ -45,7 +51,9 @@ def __init__(self, hass, entry_id, hub, name): @property def supported_features(self): - return ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + return ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) @property def name(self): diff --git a/custom_components/peaqhvac/config_flow.py b/custom_components/peaqhvac/config_flow.py index 6915254d..ff019540 100644 --- a/custom_components/peaqhvac/config_flow.py +++ b/custom_components/peaqhvac/config_flow.py @@ -1,15 +1,21 @@ """Config flow for Peaq integration.""" + from __future__ import annotations import logging from typing import Any, Optional -import homeassistant.helpers.config_validation as cv # pylint: disable=E0401 +import homeassistant.helpers.config_validation as cv # pylint: disable=E0401 import voluptuous as vol -from homeassistant import config_entries # pylint: disable=E0401 +from homeassistant import config_entries # pylint: disable=E0401 from homeassistant.core import callback # pylint: disable=E0401 -from custom_components.peaqhvac.configflow.config_flow_schemas import USER_SCHEMA, OPTIONAL_SCHEMA -from custom_components.peaqhvac.configflow.config_flow_validation import ConfigFlowValidation +from custom_components.peaqhvac.configflow.config_flow_schemas import ( + USER_SCHEMA, + OPTIONAL_SCHEMA, +) +from custom_components.peaqhvac.configflow.config_flow_validation import ( + ConfigFlowValidation, +) from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -46,7 +52,10 @@ async def async_step_optional(self, user_input=None): return self.async_create_entry(title=self.info["title"], data=self.data) return self.async_show_form( - step_id="optional", data_schema=OPTIONAL_SCHEMA, errors=errors, last_step=True + step_id="optional", + data_schema=OPTIONAL_SCHEMA, + errors=errors, + last_step=True, ) @@ -71,23 +80,38 @@ async def async_step_init(self, user_input=None): _indoortemps = await self._get_existing_param("indoor_tempsensors", "") _outdoortemps = await self._get_existing_param("outdoor_tempsensors", "") - _stopheatingtemp = await self._get_existing_param("outdoor_temp_stop_heating", 15) - _nonhours_waterboost = await self._get_existing_param("non_hours_water_boost", []) - _demandhours_waterboost = await self._get_existing_param("demand_hours_water_boost", []) + _stopheatingtemp = await self._get_existing_param( + "outdoor_temp_stop_heating", 15 + ) + _nonhours_waterboost = await self._get_existing_param( + "non_hours_water_boost", [] + ) + _demandhours_waterboost = await self._get_existing_param( + "demand_hours_water_boost", [] + ) _lowdm = await self._get_existing_param("low_degree_minutes", "-600") _verycoldtemp = await self._get_existing_param("very_cold_temp", "-12") return self.async_show_form( step_id="init", last_step=True, - data_schema=vol.Schema({ - vol.Optional("indoor_tempsensors", default=_indoortemps): cv.string, - vol.Optional("outdoor_tempsensors", default=_outdoortemps): cv.string, - vol.Optional("outdoor_temp_stop_heating", default=_stopheatingtemp): cv.positive_int, - vol.Optional("demand_hours_water_boost", default=_demandhours_waterboost): cv.multi_select( - list(range(0, 24))), - vol.Optional("non_hours_water_boost", default=_nonhours_waterboost): cv.multi_select(list(range(0, 24))), - vol.Optional("low_degree_minutes", default=_lowdm): cv.string, - vol.Optional("very_cold_temp", default=_verycoldtemp): cv.string, - }) - ) \ No newline at end of file + data_schema=vol.Schema( + { + vol.Optional("indoor_tempsensors", default=_indoortemps): cv.string, + vol.Optional( + "outdoor_tempsensors", default=_outdoortemps + ): cv.string, + vol.Optional( + "outdoor_temp_stop_heating", default=_stopheatingtemp + ): cv.positive_int, + vol.Optional( + "demand_hours_water_boost", default=_demandhours_waterboost + ): cv.multi_select(list(range(0, 24))), + vol.Optional( + "non_hours_water_boost", default=_nonhours_waterboost + ): cv.multi_select(list(range(0, 24))), + vol.Optional("low_degree_minutes", default=_lowdm): cv.string, + vol.Optional("very_cold_temp", default=_verycoldtemp): cv.string, + } + ), + ) diff --git a/custom_components/peaqhvac/configflow/config_flow_schemas.py b/custom_components/peaqhvac/configflow/config_flow_schemas.py index 917c55d4..da275172 100644 --- a/custom_components/peaqhvac/configflow/config_flow_schemas.py +++ b/custom_components/peaqhvac/configflow/config_flow_schemas.py @@ -9,11 +9,16 @@ } ) -OPTIONAL_SCHEMA = vol.Schema({ - vol.Optional("outdoor_temp_stop_heating", default=15): cv.positive_int, - vol.Optional("non_hours_water_boost", default=[7, 11, 12, 15, 16, 17, 23]): cv.multi_select(list(range(0, 24))), - vol.Optional("demand_hours_water_boost", default=[]): cv.multi_select(list(range(0, 24))), - vol.Optional("low_degree_minutes", default="-600"): cv.string, - vol.Optional("very_cold_temp", default="-12"): cv.string, -}) - +OPTIONAL_SCHEMA = vol.Schema( + { + vol.Optional("outdoor_temp_stop_heating", default=15): cv.positive_int, + vol.Optional( + "non_hours_water_boost", default=[7, 11, 12, 15, 16, 17, 23] + ): cv.multi_select(list(range(0, 24))), + vol.Optional("demand_hours_water_boost", default=[]): cv.multi_select( + list(range(0, 24)) + ), + vol.Optional("low_degree_minutes", default="-600"): cv.string, + vol.Optional("very_cold_temp", default="-12"): cv.string, + } +) diff --git a/custom_components/peaqhvac/extensionmethods.py b/custom_components/peaqhvac/extensionmethods.py index ed613bd3..c0c41524 100644 --- a/custom_components/peaqhvac/extensionmethods.py +++ b/custom_components/peaqhvac/extensionmethods.py @@ -61,6 +61,7 @@ def _parse_to_type_bool(value) -> bool: _LOGGER.error(msg) return False + def dt_from_epoch(epoch: int) -> str: return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch)) diff --git a/custom_components/peaqhvac/number.py b/custom_components/peaqhvac/number.py index 58d94ed9..3c40ffb8 100644 --- a/custom_components/peaqhvac/number.py +++ b/custom_components/peaqhvac/number.py @@ -2,9 +2,9 @@ import logging -from homeassistant.components.number import NumberEntity # pylint: disable=E0401 -from homeassistant.core import HomeAssistant # pylint: disable=E0401 -from homeassistant.helpers.restore_state import RestoreEntity # pylint: disable=E0401 +from homeassistant.components.number import NumberEntity # pylint: disable=E0401 +from homeassistant.core import HomeAssistant # pylint: disable=E0401 +from homeassistant.helpers.restore_state import RestoreEntity # pylint: disable=E0401 from peaqevcore.common.models.observer_types import ObserverTypes from .const import DOMAIN diff --git a/custom_components/peaqhvac/sensor.py b/custom_components/peaqhvac/sensor.py index 8dd343eb..554ca3ce 100644 --- a/custom_components/peaqhvac/sensor.py +++ b/custom_components/peaqhvac/sensor.py @@ -1,12 +1,22 @@ """Platform for sensor integration.""" + import logging from datetime import timedelta -from homeassistant.config_entries import ConfigEntry # pylint: disable=E0401 -from homeassistant.core import HomeAssistant # pylint: disable=E0401 - -from .const import AVERAGESENSORS, DEMANDSENSORS, DOMAIN, NEXT_WATER_START, LATEST_WATER_BOOST, \ - TRENDSENSOR_DM, TRENDSENSOR_OUTDOORS, TRENDSENSOR_INDOORS, TRENDSENSOR_WATERTEMP +from homeassistant.config_entries import ConfigEntry # pylint: disable=E0401 +from homeassistant.core import HomeAssistant # pylint: disable=E0401 + +from .const import ( + AVERAGESENSORS, + DEMANDSENSORS, + DOMAIN, + NEXT_WATER_START, + LATEST_WATER_BOOST, + TRENDSENSOR_DM, + TRENDSENSOR_OUTDOORS, + TRENDSENSOR_INDOORS, + TRENDSENSOR_WATERTEMP, +) from .sensors.min_maxsensor import AverageSensor from .sensors.money_data_sensor import PeaqMoneyDataSensor from .sensors.offsetsensor import OffsetSensor @@ -33,35 +43,38 @@ async def async_setup_entry( async def _gather_sensors(hub, config) -> list: TRENDSENSORS = [ { - "name": TRENDSENSOR_INDOORS, + "name": TRENDSENSOR_INDOORS, "sensor": hub.sensors.temp_trend_indoors, - "icon": "mdi:home-thermometer", - "unit": "°C/h", + "icon": "mdi:home-thermometer", + "unit": "°C/h", }, { - "name": TRENDSENSOR_OUTDOORS, + "name": TRENDSENSOR_OUTDOORS, "sensor": hub.sensors.temp_trend_outdoors, - "icon": "mdi:sun-thermometer", - "unit": "°C/h", + "icon": "mdi:sun-thermometer", + "unit": "°C/h", }, { - "name": TRENDSENSOR_DM, + "name": TRENDSENSOR_DM, "sensor": hub.sensors.dm_trend, - "icon": "mdi:hvac", - "unit": "DM/h", + "icon": "mdi:hvac", + "unit": "DM/h", "extra_attributes": { "time_at_zero": (hub.sensors.dm_trend.predicted_time_at_value, 0) - } + }, }, { - "name": TRENDSENSOR_WATERTEMP, - "sensor": hub.hvac_service.water_heater.temp_trend, - "icon": "mdi:thermometer-water", - "unit": "°C/h", + "name": TRENDSENSOR_WATERTEMP, + "sensor": hub.hvac_service.water_heater.temp_trend, + "icon": "mdi:thermometer-water", + "unit": "°C/h", "extra_attributes": { - "time_at_40": (hub.hvac_service.water_heater.temp_trend.predicted_time_at_value, 40) - } - } + "time_at_40": ( + hub.hvac_service.water_heater.temp_trend.predicted_time_at_value, + 40, + ) + }, + }, ] ret = [] @@ -70,32 +83,49 @@ async def _gather_sensors(hub, config) -> list: for a in AVERAGESENSORS: ret.append(AverageSensor(hub, config.entry_id, a)) for sensor in TRENDSENSORS: - ret.append(TrendSensor( - hub=hub, - entry_id=config.entry_id, - name=sensor["name"], - icon=sensor["icon"], - unit_of_measurement=sensor["unit"], - sensor=sensor["sensor"], - extra_attributes=sensor.get("extra_attributes", {}) -)) + ret.append( + TrendSensor( + hub=hub, + entry_id=config.entry_id, + name=sensor["name"], + icon=sensor["icon"], + unit_of_measurement=sensor["unit"], + sensor=sensor["sensor"], + extra_attributes=sensor.get("extra_attributes", {}), + ) + ) for key in DEMANDSENSORS: ret.append(PeaqSensor(hub, config.entry_id, key, DEMANDSENSORS[key])) - ret.append(PeaqSimpleSensor(hub, config.entry_id, "next water start", NEXT_WATER_START, "mdi:clock-start")) - ret.append(PeaqSimpleSensor(hub, config.entry_id, "latest water boost", LATEST_WATER_BOOST, "mdi:clock-end")) + ret.append( + PeaqSimpleSensor( + hub, + config.entry_id, + "next water start", + NEXT_WATER_START, + "mdi:clock-start", + ) + ) + ret.append( + PeaqSimpleSensor( + hub, + config.entry_id, + "latest water boost", + LATEST_WATER_BOOST, + "mdi:clock-end", + ) + ) if not hub.options.misc_options.peaqev_discovered: - simplesensors = [("Average price this month", "average_month"), - ("Average price 7 days", "average_weekly"), - ("Average price 30 days", "average_30"), - ("Average price 3 days", "average_three_days")] + simplesensors = [ + ("Average price this month", "average_month"), + ("Average price 7 days", "average_weekly"), + ("Average price 30 days", "average_30"), + ("Average price 3 days", "average_three_days"), + ] for name, attr in simplesensors: - ret.append( - PeaqSimpleMoneySensor( - hub, config.entry_id, name, attr) - ) + ret.append(PeaqSimpleMoneySensor(hub, config.entry_id, name, attr)) _LOGGER.debug(f"Setting up sensor for {name} with attr {attr}") ret.append(PeaqMoneyDataSensor(hub, config.entry_id)) diff --git a/custom_components/peaqhvac/sensors/const.py b/custom_components/peaqhvac/sensors/const.py index b99875d8..415199c1 100644 --- a/custom_components/peaqhvac/sensors/const.py +++ b/custom_components/peaqhvac/sensors/const.py @@ -1,2 +1,2 @@ -MONEYCONTROLS ="Money Controls" -AVERAGE_SPOTPRICE_DATA = "Average spotprice data" \ No newline at end of file +MONEYCONTROLS = "Money Controls" +AVERAGE_SPOTPRICE_DATA = "Average spotprice data" diff --git a/custom_components/peaqhvac/sensors/min_maxsensor.py b/custom_components/peaqhvac/sensors/min_maxsensor.py index d2add85e..fab9e2b8 100644 --- a/custom_components/peaqhvac/sensors/min_maxsensor.py +++ b/custom_components/peaqhvac/sensors/min_maxsensor.py @@ -1,8 +1,10 @@ from homeassistant.components.sensor import SensorStateClass from homeassistant.helpers.restore_state import RestoreEntity -from custom_components.peaqhvac.const import (AVERAGESENSOR_INDOORS, - AVERAGESENSOR_OUTDOORS) +from custom_components.peaqhvac.const import ( + AVERAGESENSOR_INDOORS, + AVERAGESENSOR_OUTDOORS, +) from custom_components.peaqhvac.sensors.sensorbase import SensorBase diff --git a/custom_components/peaqhvac/sensors/money_data_sensor.py b/custom_components/peaqhvac/sensors/money_data_sensor.py index 14b1c832..971b2df9 100644 --- a/custom_components/peaqhvac/sensors/money_data_sensor.py +++ b/custom_components/peaqhvac/sensors/money_data_sensor.py @@ -5,7 +5,10 @@ from custom_components.peaqhvac import DOMAIN from custom_components.peaqhvac.extensionmethods import nametoid -from custom_components.peaqhvac.sensors.const import MONEYCONTROLS, AVERAGE_SPOTPRICE_DATA +from custom_components.peaqhvac.sensors.const import ( + MONEYCONTROLS, + AVERAGE_SPOTPRICE_DATA, +) if TYPE_CHECKING: from custom_components.peaqhvac.service.hub.hub import Hub @@ -21,7 +24,7 @@ class PeaqMoneyDataSensor(SensorEntity, RestoreEntity): def __init__(self, hub: Hub, entry_id): name = f"{hub.hubname} {AVERAGE_SPOTPRICE_DATA}" - #super().__init__(hub, name, entry_id) + # super().__init__(hub, name, entry_id) self.hub = hub self._entry_id = entry_id @@ -43,7 +46,9 @@ async def async_update(self) -> None: self._state = "on" if ret != self._average_spotprice_data: _diff = self.diff_dicts(self._average_spotprice_data, ret) - _LOGGER.debug(f"dict was changed: added: {_diff[0]}, removed: {_diff[1]}") + _LOGGER.debug( + f"dict was changed: added: {_diff[0]}, removed: {_diff[1]}" + ) self._average_spotprice_data = ret self.hub.spotprice.converted_average_data = True @@ -64,7 +69,7 @@ def diff_dicts(dict1, dict2): @property def extra_state_attributes(self) -> dict: - attr_dict = {"Spotprice average data": self._average_spotprice_data } + attr_dict = {"Spotprice average data": self._average_spotprice_data} return attr_dict async def async_added_to_hass(self): @@ -81,9 +86,9 @@ async def async_added_to_hass(self): @property def device_info(self): return { - "identifiers": {(DOMAIN, self.hub.hub_id, MONEYCONTROLS)}, - "name": f"{DOMAIN} {MONEYCONTROLS}", - "sw_version": 1, + "identifiers": {(DOMAIN, self.hub.hub_id, MONEYCONTROLS)}, + "name": f"{DOMAIN} {MONEYCONTROLS}", + "sw_version": 1, "manufacturer": "Peaq systems", } diff --git a/custom_components/peaqhvac/sensors/offsetsensor.py b/custom_components/peaqhvac/sensors/offsetsensor.py index e0185d0f..0f2dfc26 100644 --- a/custom_components/peaqhvac/sensors/offsetsensor.py +++ b/custom_components/peaqhvac/sensors/offsetsensor.py @@ -1,6 +1,10 @@ from custom_components.peaqhvac.sensors.sensorbase import SensorBase -from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import CalculatedOffsetModel -from custom_components.peaqhvac.service.models.offsets_exportmodel import OffsetsExportModel +from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import ( + CalculatedOffsetModel, +) +from custom_components.peaqhvac.service.models.offsets_exportmodel import ( + OffsetsExportModel, +) class OffsetSensor(SensorBase): @@ -34,11 +38,15 @@ def icon(self) -> str: return "mdi:stairs" async def async_update(self) -> None: - state_result = await self._hub.hvac_service.house_heater.async_adjusted_offset(self._hub.hvac_service.model.current_offset) + state_result = await self._hub.hvac_service.house_heater.async_adjusted_offset( + self._hub.hvac_service.model.current_offset + ) self._state = state_result[0] offsetsmodel: OffsetsExportModel = await self._hub.async_offset_export_model() - data: CalculatedOffsetModel = await self._hub.hvac_service.house_heater.async_calculated_offsetdata( - self._hub.hvac_service.model.current_offset + data: CalculatedOffsetModel = ( + await self._hub.hvac_service.house_heater.async_calculated_offsetdata( + self._hub.hvac_service.model.current_offset + ) ) self._offsets = offsetsmodel.current_offset @@ -55,15 +63,15 @@ async def async_update(self) -> None: @property def extra_state_attributes(self) -> dict: ret = { - "Current hour offset": self._current_offset, - "Tempdiff offset": self._tempdiff_offset, + "Current hour offset": self._current_offset, + "Tempdiff offset": self._tempdiff_offset, "Temp extremas offset": self._tempextremas_offset, - "Temp trend offset": self._temptrend_offset, - "Today": self._offsets, - "Tomorrow": self._offsets_tomorrow, - "Raw": self._raw_offsets, - "PeaksToday": self._peaks_today, - "PeaksTomorrow": self._peaks_tomorrow, + "Temp trend offset": self._temptrend_offset, + "Today": self._offsets, + "Tomorrow": self._offsets_tomorrow, + "Raw": self._raw_offsets, + "PeaksToday": self._peaks_today, + "PeaksTomorrow": self._peaks_tomorrow, } if self._aux_dict is not None: for key, val in self._aux_dict.items(): diff --git a/custom_components/peaqhvac/sensors/peaqsensor.py b/custom_components/peaqhvac/sensors/peaqsensor.py index 68386346..56fda345 100644 --- a/custom_components/peaqhvac/sensors/peaqsensor.py +++ b/custom_components/peaqhvac/sensors/peaqsensor.py @@ -43,7 +43,9 @@ async def async_update(self) -> None: elif self._sensorname == WATERDEMAND: self._state = self._hub.hvac_service.water_heater.demand.value self._current_water_temperature = self._hub.hvac_service.hvac_watertemp - self._heat_water = self._hub.hvac_service.water_heater.model.water_boost.value + self._heat_water = ( + self._hub.hvac_service.water_heater.model.water_boost.value + ) self._water_is_heating = self._hub.hvac_service.water_heater.water_heating async def async_added_to_hass(self): diff --git a/custom_components/peaqhvac/sensors/simple_money_sensor.py b/custom_components/peaqhvac/sensors/simple_money_sensor.py index 5d2b8b77..aa4f1999 100644 --- a/custom_components/peaqhvac/sensors/simple_money_sensor.py +++ b/custom_components/peaqhvac/sensors/simple_money_sensor.py @@ -17,13 +17,12 @@ _LOGGER = logging.getLogger(__name__) - class PeaqSimpleMoneySensor(SensorEntity): device_class = SensorDeviceClass.MONETARY def __init__(self, hub: Hub, entry_id, sensor_name: str, caller_attribute: str): name = f"{hub.hubname} {sensor_name}" - #super().__init__(hub, name, entry_id) + # super().__init__(hub, name, entry_id) self._attr_name = name self._entry_id = entry_id @@ -54,10 +53,7 @@ async def async_update(self) -> None: @property def extra_state_attributes(self) -> dict: - return { - "Use Cent": self._use_cent, - "Price Source": self.hub.spotprice.source - } + return {"Use Cent": self._use_cent, "Price Source": self.hub.spotprice.source} @property def device_info(self): @@ -72,4 +68,3 @@ def device_info(self): def unique_id(self): """Return a unique ID to use for this sensor.""" return f"{DOMAIN}_{self._entry_id}_{nametoid(self._attr_name)}" - diff --git a/custom_components/peaqhvac/sensors/simple_sensor.py b/custom_components/peaqhvac/sensors/simple_sensor.py index c370fb5a..f1349d7a 100644 --- a/custom_components/peaqhvac/sensors/simple_sensor.py +++ b/custom_components/peaqhvac/sensors/simple_sensor.py @@ -2,14 +2,27 @@ from homeassistant.helpers.restore_state import RestoreEntity -from custom_components.peaqhvac.const import HEATINGDEMAND, WATERDEMAND, NEXT_WATER_START, LATEST_WATER_BOOST +from custom_components.peaqhvac.const import ( + HEATINGDEMAND, + WATERDEMAND, + NEXT_WATER_START, + LATEST_WATER_BOOST, +) from custom_components.peaqhvac.sensors.sensorbase import SensorBase import logging _LOGGER = logging.getLogger(__name__) + class PeaqSimpleSensor(SensorBase, RestoreEntity): - def __init__(self, hub, entry_id, name: str, internal_entity: str, icon: str = "mdi:clock-end"): + def __init__( + self, + hub, + entry_id, + name: str, + internal_entity: str, + icon: str = "mdi:clock-end", + ): self._sensorname = name self._icon = icon self._attr_name = f"{hub.hubname} {name.capitalize()}" @@ -44,7 +57,9 @@ async def async_added_to_hass(self): if state: self._state = state.state if self._internal_entity == LATEST_WATER_BOOST: - self._hub.hvac_service.water_heater.import_latest_boost_call(self._state) + self._hub.hvac_service.water_heater.import_latest_boost_call( + self._state + ) self._hub.hvac_service.water_heater.is_initialized = True else: self._state = "-" diff --git a/custom_components/peaqhvac/sensors/trendsensor.py b/custom_components/peaqhvac/sensors/trendsensor.py index 3cbdbcc3..82f10ae7 100644 --- a/custom_components/peaqhvac/sensors/trendsensor.py +++ b/custom_components/peaqhvac/sensors/trendsensor.py @@ -6,8 +6,18 @@ _LOGGER = logging.getLogger(__name__) + class TrendSensor(SensorBase, RestoreEntity): - def __init__(self, hub, entry_id, name, icon, unit_of_measurement, sensor, extra_attributes ={}): + def __init__( + self, + hub, + entry_id, + name, + icon, + unit_of_measurement, + sensor, + extra_attributes={}, + ): self._sensorname = name self.datasensor = sensor self._attr_name = f"{hub.hubname} {name}" diff --git a/custom_components/peaqhvac/service/hub/average.py b/custom_components/peaqhvac/service/hub/average.py index 110ec663..ebdd75c7 100644 --- a/custom_components/peaqhvac/service/hub/average.py +++ b/custom_components/peaqhvac/service/hub/average.py @@ -3,13 +3,17 @@ from peaqevcore.common.models.observer_types import ObserverTypes -from custom_components.peaqhvac.service.observer.observer_broadcaster import ObserverBroadcaster +from custom_components.peaqhvac.service.observer.observer_broadcaster import ( + ObserverBroadcaster, +) _LOGGER = logging.getLogger(__name__) class Average(ObserverBroadcaster): - def __init__(self, entities: list[str], observer, observer_message: ObserverTypes = None): + def __init__( + self, entities: list[str], observer, observer_message: ObserverTypes = None + ): self.listenerentities = entities self._value: float = 0.0 self._median: float = 0.0 diff --git a/custom_components/peaqhvac/service/hub/hub.py b/custom_components/peaqhvac/service/hub/hub.py index 03ff31ec..ae57c0ad 100644 --- a/custom_components/peaqhvac/service/hub/hub.py +++ b/custom_components/peaqhvac/service/hub/hub.py @@ -3,12 +3,19 @@ from functools import partial from typing import Callable -from homeassistant.core import HomeAssistant, callback, Event, EventStateChangedData # pylint: disable=E0401 +from homeassistant.core import ( + HomeAssistant, + callback, + Event, + EventStateChangedData, +) # pylint: disable=E0401 from custom_components.peaqhvac.const import LATEST_WATER_BOOST, NEXT_WATER_START from custom_components.peaqhvac.service.models.config_model import ConfigModel -from custom_components.peaqhvac.service.models.offsets_exportmodel import OffsetsExportModel +from custom_components.peaqhvac.service.models.offsets_exportmodel import ( + OffsetsExportModel, +) from custom_components.peaqhvac.service.observer.iobserver_coordinator import IObserver from custom_components.peaqhvac.extensionmethods import async_iscoroutine @@ -19,7 +26,9 @@ class Hub: hub_id = 1338 hubname = "PeaqHvac" - def __init__(self, hass: HomeAssistant, observer: IObserver, hub_options: ConfigModel): + def __init__( + self, hass: HomeAssistant, observer: IObserver, hub_options: ConfigModel + ): self.trackerentities = [] self._is_initialized = False self.state_machine = hass @@ -34,10 +43,14 @@ def __init__(self, hass: HomeAssistant, observer: IObserver, hub_options: Config def price_below_min(self, hour: datetime) -> bool: try: - return self.spotprice.model.prices[hour.hour] <= self.sensors.peaqev_facade.min_price + return ( + self.spotprice.model.prices[hour.hour] + <= self.sensors.peaqev_facade.min_price + ) except: _LOGGER.warning( - f"Unable to get price for hour {hour}. min_price: {self.sensors.peaqev_facade.min_price}, num_prices_today: {len(self.spotprice.model.prices)}") + f"Unable to get price for hour {hour}. min_price: {self.sensors.peaqev_facade.min_price}, num_prices_today: {len(self.spotprice.model.prices)}" + ) return False @property @@ -64,7 +77,8 @@ async def async_on_change(self, event: Event[EventStateChangedData]) -> None: await self.states.async_update_sensor(entity_id, new_state.state) except Exception as e: _LOGGER.exception( - f"Unable to handle data: {entity_id} old: {old_state}, new: {new_state}. Raised expection: {e}") + f"Unable to handle data: {entity_id} old: {old_state}, new: {new_state}. Raised expection: {e}" + ) async def call_enable_peaq(self): self.sensors.peaqhvac_enabled.value = True @@ -78,8 +92,12 @@ async def call_set_mode(self, mode): async def async_get_internal_sensor(self, entity): lookup = { - LATEST_WATER_BOOST: partial(getattr, self.hvac_service.water_heater, "latest_boost_call"), - NEXT_WATER_START: partial(getattr, self.hvac_service.water_heater, "next_water_heater_start") + LATEST_WATER_BOOST: partial( + getattr, self.hvac_service.water_heater, "latest_boost_call" + ), + NEXT_WATER_START: partial( + getattr, self.hvac_service.water_heater, "next_water_heater_start" + ), } func: Callable = lookup.get(entity, None) @@ -90,7 +108,8 @@ async def async_get_internal_sensor(self, entity): async def async_offset_export_model(self) -> OffsetsExportModel: ret = OffsetsExportModel( - (self.offset.model.peaks_today, self.offset.model.peaks_tomorrow)) + (self.offset.model.peaks_today, self.offset.model.peaks_tomorrow) + ) ret.raw_offsets = self.offset.model.raw_offsets ret.current_offset = self.offset.model.current_offset_dict ret.current_offset_tomorrow = self.offset.model.current_offset_dict_tomorrow diff --git a/custom_components/peaqhvac/service/hub/hub_factory.py b/custom_components/peaqhvac/service/hub/hub_factory.py index 96f91e12..e74e041d 100644 --- a/custom_components/peaqhvac/service/hub/hub_factory.py +++ b/custom_components/peaqhvac/service/hub/hub_factory.py @@ -8,9 +8,12 @@ from custom_components.peaqhvac.service.hub.hubsensors import HubSensors from custom_components.peaqhvac.service.hub.state_changes import StateChanges from custom_components.peaqhvac.service.hub.weather_prognosis import WeatherPrognosis -from custom_components.peaqhvac.service.hvac.offset.offset_coordinator_factory import OffsetFactory +from custom_components.peaqhvac.service.hvac.offset.offset_coordinator_factory import ( + OffsetFactory, +) import sys -if 'pytest' not in sys.modules: + +if "pytest" not in sys.modules: from peaqevcore.common.spotprice.spotprice_factory import SpotPriceFactory from peaqevcore.common.models.peaq_system import PeaqSystem @@ -33,13 +36,9 @@ async def async_create(self, options: ConfigModel) -> Hub: observer=observer, system=PeaqSystem.PeaqHvac, test=False, - is_active=True + is_active=True, ) - sensors = HubSensors( - observer=observer, - options=options, - hass=self.hass - ) + sensors = HubSensors(observer=observer, options=options, hass=self.hass) states = StateChanges(hub, self.hass) self.hub = hub await self.async_setup(spotprice, sensors, states) @@ -50,9 +49,13 @@ async def async_setup(self, spotprice, sensors, states) -> None: self.hub.states = states self.hub.spotprice = spotprice - self.hub.hvac_service = HvacFactory.create(self.hass, self.hub.options, self.hub, self.hub.observer) + self.hub.hvac_service = HvacFactory.create( + self.hass, self.hub.options, self.hub, self.hub.observer + ) - self.hub.prognosis = WeatherPrognosis(self.hass, sensors.average_temp_outdoors, self.hub.observer) + self.hub.prognosis = WeatherPrognosis( + self.hass, sensors.average_temp_outdoors, self.hub.observer + ) self.hub.offset = OffsetFactory.create(self.hub, self.hub.observer) await self.async_setup_trackers() @@ -71,10 +74,16 @@ def _get_peaqev(self): ret = self.hass.states.get("sensor.peaqev_threshold") if ret is not None: if ret.state: - _LOGGER.debug("Discovered Peaqev-entities, will adhere to peak-shaving.") + _LOGGER.debug( + "Discovered Peaqev-entities, will adhere to peak-shaving." + ) return True - _LOGGER.debug("Unable to discover Peaqev-entities, will not adhere to peak-shaving.") + _LOGGER.debug( + "Unable to discover Peaqev-entities, will not adhere to peak-shaving." + ) return False except Exception as e: - _LOGGER.debug(f"Unable to discover Peaqev-entities, will not adhere to peak-shaving. Exception {e}") + _LOGGER.debug( + f"Unable to discover Peaqev-entities, will not adhere to peak-shaving. Exception {e}" + ) return False diff --git a/custom_components/peaqhvac/service/hub/hubsensors.py b/custom_components/peaqhvac/service/hub/hubsensors.py index d1c15897..ddb98174 100644 --- a/custom_components/peaqhvac/service/hub/hubsensors.py +++ b/custom_components/peaqhvac/service/hub/hubsensors.py @@ -7,10 +7,14 @@ from custom_components.peaqhvac.service.hub.target_temp import TargetTemp from peaqevcore.common.trend import Gradient from custom_components.peaqhvac.service.models.config_model import ConfigModel -from custom_components.peaqhvac.service.peaqev_facade import PeaqevFacade, PeaqevFacadeBase +from custom_components.peaqhvac.service.peaqev_facade import ( + PeaqevFacade, + PeaqevFacadeBase, +) _LOGGER = logging.getLogger(__name__) + class HubSensors: peaqhvac_enabled: HubMember temp_trend_outdoors: Gradient @@ -34,13 +38,17 @@ def __init__(self, observer, options: ConfigModel, hass): observer_message=ObserverTypes.TemperatureOutdoorsChanged, observer=observer, ) - self.temp_trend_indoors = Gradient(max_samples=100, max_age=7200, precision=1, outlier=1, ignore=0) - self.temp_trend_outdoors = Gradient(max_samples=100, max_age=7200, precision=1, outlier=1) + self.temp_trend_indoors = Gradient( + max_samples=100, max_age=7200, precision=1, outlier=1, ignore=0 + ) + self.temp_trend_outdoors = Gradient( + max_samples=100, max_age=7200, precision=1, outlier=1 + ) self.dm_trend = Gradient(max_age=3600, max_samples=100, precision=0) self.set_temp_indoors = TargetTemp( observer_message=ObserverTypes.SetTemperatureChanged, average_temp_outdoors=self.average_temp_outdoors, - observer=observer + observer=observer, ) if options.misc_options.peaqev_discovered: @@ -52,10 +60,7 @@ def __init__(self, observer, options: ConfigModel, hass): @property def predicted_temp(self) -> float: - return ( - self.average_temp_indoors.value - + self.temp_trend_indoors.trend - ) + return self.average_temp_indoors.value + self.temp_trend_indoors.trend @property def tolerances(self) -> Tuple[float, float]: @@ -71,4 +76,4 @@ def get_tempdiff(self) -> float: def get_tempdiff_in_out(self) -> float: _indoors = getattr(self.average_temp_indoors, "value", 0) _outdoors = getattr(self.average_temp_outdoors, "value", 0) - return _indoors - _outdoors \ No newline at end of file + return _indoors - _outdoors diff --git a/custom_components/peaqhvac/service/hub/state_changes.py b/custom_components/peaqhvac/service/hub/state_changes.py index 33e643d6..3a1be96a 100644 --- a/custom_components/peaqhvac/service/hub/state_changes.py +++ b/custom_components/peaqhvac/service/hub/state_changes.py @@ -24,14 +24,20 @@ async def async_initialize_values(self): await self.async_update_sensor(entity=t, value=retval.state) async def _update_indoor_sensor(self, entity, value): - await self._hub.sensors.average_temp_indoors.async_update_values(entity=entity, value=value) - await self._hub.sensors.temp_trend_indoors.async_add_reading(val=self._hub.sensors.average_temp_indoors.value, - t=time.time()) + await self._hub.sensors.average_temp_indoors.async_update_values( + entity=entity, value=value + ) + await self._hub.sensors.temp_trend_indoors.async_add_reading( + val=self._hub.sensors.average_temp_indoors.value, t=time.time() + ) async def _update_outdoor_sensor(self, entity, value): - await self._hub.sensors.average_temp_outdoors.async_update_values(entity=entity, value=value) - await self._hub.sensors.temp_trend_outdoors.async_add_reading(val=self._hub.sensors.average_temp_outdoors.value, - t=time.time()) + await self._hub.sensors.average_temp_outdoors.async_update_values( + entity=entity, value=value + ) + await self._hub.sensors.temp_trend_outdoors.async_add_reading( + val=self._hub.sensors.average_temp_outdoors.value, t=time.time() + ) async def async_update_sensor(self, entity, value): if entity in self._hub.options.indoor_tempsensors: @@ -39,12 +45,19 @@ async def async_update_sensor(self, entity, value): elif entity in self._hub.options.outdoor_tempsensors: await self._update_outdoor_sensor(entity, value) - await self._hass.async_add_executor_job(self._hub.prognosis.get_hvac_prognosis, - self._hub.sensors.average_temp_outdoors.value) + await self._hass.async_add_executor_job( + self._hub.prognosis.get_hvac_prognosis, + self._hub.sensors.average_temp_outdoors.value, + ) - if entity == self._hub.spotprice.entity or self.latest_nordpool_update.is_timeout(): + if ( + entity == self._hub.spotprice.entity + or self.latest_nordpool_update.is_timeout() + ): await self._hub.spotprice.async_update_spotprice() - await self._hass.async_add_executor_job(self._hub.prognosis.update_weather_prognosis) + await self._hass.async_add_executor_job( + self._hub.prognosis.update_weather_prognosis + ) self.latest_nordpool_update.update() await self._hub.hvac_service.async_update_hvac() diff --git a/custom_components/peaqhvac/service/hub/target_temp.py b/custom_components/peaqhvac/service/hub/target_temp.py index b7ecc4a8..7503444c 100644 --- a/custom_components/peaqhvac/service/hub/target_temp.py +++ b/custom_components/peaqhvac/service/hub/target_temp.py @@ -3,30 +3,30 @@ from peaqevcore.common.models.observer_types import ObserverTypes -from custom_components.peaqhvac.service.models.enums.hvac_presets import \ - HvacPresets -from custom_components.peaqhvac.service.observer.observer_broadcaster import ObserverBroadcaster +from custom_components.peaqhvac.service.models.enums.hvac_presets import HvacPresets +from custom_components.peaqhvac.service.observer.observer_broadcaster import ( + ObserverBroadcaster, +) MINTEMP = 15 MAXTEMP = 27 _LOGGER = logging.getLogger(__name__) -def adjusted_tolerances(offset: int, min_tolerance, max_tolerance) -> Tuple[float, float]: +def adjusted_tolerances( + offset: int, min_tolerance, max_tolerance +) -> Tuple[float, float]: # if abs(offset) <= 1: return min_tolerance, max_tolerance - _max_tolerance = ( - max_tolerance + (offset / 15) if offset > 0 else max_tolerance - ) - _min_tolerance = ( - min_tolerance + (abs(offset) / 10) - if offset < 0 - else min_tolerance - ) + _max_tolerance = max_tolerance + (offset / 15) if offset > 0 else max_tolerance + _min_tolerance = min_tolerance + (abs(offset) / 10) if offset < 0 else min_tolerance return max(_min_tolerance, 0.1), max(_max_tolerance, 0.1) + class TargetTemp(ObserverBroadcaster): - def __init__(self, average_temp_outdoors, observer, initval=19, observer_message: str = None): + def __init__( + self, average_temp_outdoors, observer, initval=19, observer_message: str = None + ): self._average_temp_outdoors_sensor = average_temp_outdoors self._value = initval self._min_tolerance = None @@ -54,7 +54,9 @@ def value(self, val) -> None: try: if val is not None: self._value = val - self._internal_set_temp = self._value - HvacPresets.get_tempdiff(self.preset) + self._internal_set_temp = self._value - HvacPresets.get_tempdiff( + self.preset + ) self._adjusted_value = self.adjusted_temp self._set_temperature_and_tolerances() self._broadcast_changes() @@ -103,8 +105,6 @@ def _init_tolerances(self, preset: HvacPresets = HvacPresets.Normal): self._min_tolerance = _tolerances[0] self._max_tolerance = _tolerances[1] - - def _minmax(self, desired_temp) -> float: if desired_temp < MINTEMP: return MINTEMP diff --git a/custom_components/peaqhvac/service/hub/weather_prognosis.py b/custom_components/peaqhvac/service/hub/weather_prognosis.py index 0b7fb71c..c8366fc0 100644 --- a/custom_components/peaqhvac/service/hub/weather_prognosis.py +++ b/custom_components/peaqhvac/service/hub/weather_prognosis.py @@ -7,10 +7,10 @@ import homeassistant.helpers.template as template from peaqevcore.common.models.observer_types import ObserverTypes -from custom_components.peaqhvac.service.models.prognosis_export_model import \ - PrognosisExportModel -from custom_components.peaqhvac.service.models.weather_object import \ - WeatherObject +from custom_components.peaqhvac.service.models.prognosis_export_model import ( + PrognosisExportModel, +) +from custom_components.peaqhvac.service.models.weather_object import WeatherObject _LOGGER = logging.getLogger(__name__) @@ -38,9 +38,7 @@ def prognosis(self) -> list: self.update_weather_prognosis() if len(self._hvac_prognosis_list) == 0: try: - return self.get_hvac_prognosis( - self.average_temp_outdoors.value - ) + return self.get_hvac_prognosis(self.average_temp_outdoors.value) except Exception as e: _LOGGER.warning(f"Could not get hvac-prognosis: {e}") return [] @@ -54,11 +52,13 @@ def update_weather_prognosis(self): # data: # type: hourly try: - ret = self._hass.services.call("weather", "get_forecasts", {"type": "hourly"}) + ret = self._hass.services.call( + "weather", "get_forecasts", {"type": "hourly"} + ) except Exception as e: _LOGGER.error(f"Could not get weather-prognosis: {e}") return - #ret = self._hass.states.get(self.entity) + # ret = self._hass.states.get(self.entity) if ret is not None: try: ret_attr = list(ret.attributes.get("forecast")) @@ -73,10 +73,18 @@ def update_weather_prognosis(self): else: _LOGGER.error("could not get weather-prognosis.") - def get_weatherprognosis_adjustment(self, offsets:dict[datetime, int]) -> dict: + def get_weatherprognosis_adjustment(self, offsets: dict[datetime, int]) -> dict: self.update_weather_prognosis() - ret = {k:v for k,v in offsets.items() if k.date == datetime.now().date()+timedelta(days=1)} - rr = {k:self._get_weatherprognosis_hourly_adjustment(k.hour, v) for k,v in offsets.items() if k.date == datetime.now().date()} + ret = { + k: v + for k, v in offsets.items() + if k.date == datetime.now().date() + timedelta(days=1) + } + rr = { + k: self._get_weatherprognosis_hourly_adjustment(k.hour, v) + for k, v in offsets.items() + if k.date == datetime.now().date() + } ret.update(rr) return ret # for hour, offset in offsets[0].items(): @@ -95,9 +103,7 @@ def get_hvac_prognosis(self, current_temperature: float) -> list: corrected_temp_delta = 0 now = datetime.now().replace(minute=0, second=0, microsecond=0) - valid_progs = [ - p for idx, p in enumerate(self.prognosis_list) if p.DT >= now - ] + valid_progs = [p for idx, p in enumerate(self.prognosis_list) if p.DT >= now] if len(valid_progs) == 0: return ret for p in valid_progs: @@ -112,10 +118,12 @@ def get_hvac_prognosis(self, current_temperature: float) -> list: hour_prognosis = PrognosisExportModel( prognosis_temp=p.Temperature, corrected_temp=temp, - windchill_temp=self._correct_temperature_for_windchill(temp, p.Wind_Speed), + windchill_temp=self._correct_temperature_for_windchill( + temp, p.Wind_Speed + ), DT=p.DT, TimeDelta=hourdiff, - _base_temp = self._current_temperature + _base_temp=self._current_temperature, ) ret.append(hour_prognosis) @@ -140,8 +148,17 @@ def _get_weatherprognosis_hourly_adjustment(self, hour, offset) -> int: if _next_prognosis is not None and int(hour) >= now.hour: divisor = max((11 - _next_prognosis.TimeDelta) / 10, 0) adjustment_divisor = 2.5 if _next_prognosis.windchill_temp > -2 else 2 - adj = (int(round((_next_prognosis.delta_temp_from_now / adjustment_divisor) * divisor, 0)) * -1) - #if 14 < hour < 19: + adj = ( + int( + round( + (_next_prognosis.delta_temp_from_now / adjustment_divisor) + * divisor, + 0, + ) + ) + * -1 + ) + # if 14 < hour < 19: # print(f"for {hour} the initial was {offset}, adjustment: {adj}, divisor:{divisor}, timedelta:{_next_prognosis.TimeDelta}, windchill:{_next_prognosis.windchill_temp}, deltatemp:{_next_prognosis.delta_temp_from_now}") ret = offset + adj return ret @@ -182,7 +199,9 @@ def _get_two_hour_prog(self, thishour: datetime) -> PrognosisExportModel | None: return p return None - def _setup_weather_prognosis(self): #todo: this must be handled with weeather servicecall. + def _setup_weather_prognosis( + self, + ): # todo: this must be handled with weeather servicecall. pass # try: # entities = template.integration_entities(self._hass, "met") @@ -202,4 +221,4 @@ def _setup_weather_prognosis(self): #todo: this must be handled with weeather se # pass # except Exception as e: # msg = f"Peaqev was unable to get a single weather-entity. Disabling Weather-prognosis: {e}" - # _LOGGER.error(msg) \ No newline at end of file + # _LOGGER.error(msg) diff --git a/custom_components/peaqhvac/service/hvac/const.py b/custom_components/peaqhvac/service/hvac/const.py index 5deb21ba..576fbd5d 100644 --- a/custom_components/peaqhvac/service/hvac/const.py +++ b/custom_components/peaqhvac/service/hvac/const.py @@ -1,5 +1,4 @@ - HEATBOOST_TIMER = 7200 WAITTIMER_TIMEOUT = 240 DEFAULT_WATER_BOOST = 1200 -WAITTIMER_VENT = 900 \ No newline at end of file +WAITTIMER_VENT = 900 diff --git a/custom_components/peaqhvac/service/hvac/house_heater/house_heater_coordinator.py b/custom_components/peaqhvac/service/hvac/house_heater/house_heater_coordinator.py index 0e16f86b..c8f34a91 100644 --- a/custom_components/peaqhvac/service/hvac/house_heater/house_heater_coordinator.py +++ b/custom_components/peaqhvac/service/hvac/house_heater/house_heater_coordinator.py @@ -3,13 +3,24 @@ import logging from typing import Tuple from custom_components.peaqhvac.service.hub.target_temp import adjusted_tolerances -from custom_components.peaqhvac.service.hvac.house_heater.house_heater_helpers import HouseHeaterHelpers -from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import CalculatedOffsetModel -from custom_components.peaqhvac.service.hvac.house_heater.models.offset_adjustments import OffsetAdjustments -from custom_components.peaqhvac.service.hvac.house_heater.temperature_helper import get_tempdiff_inverted, \ - get_temp_extremas, get_temp_trend_offset +from custom_components.peaqhvac.service.hvac.house_heater.house_heater_helpers import ( + HouseHeaterHelpers, +) +from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import ( + CalculatedOffsetModel, +) +from custom_components.peaqhvac.service.hvac.house_heater.models.offset_adjustments import ( + OffsetAdjustments, +) +from custom_components.peaqhvac.service.hvac.house_heater.temperature_helper import ( + get_tempdiff_inverted, + get_temp_extremas, + get_temp_trend_offset, +) from custom_components.peaqhvac.service.hvac.interfaces.iheater import IHeater -from custom_components.peaqhvac.service.hvac.offset.offset_utils import adjust_to_threshold +from custom_components.peaqhvac.service.hvac.offset.offset_utils import ( + adjust_to_threshold, +) from custom_components.peaqhvac.service.models.enums.demand import Demand _LOGGER = logging.getLogger(__name__) @@ -47,10 +58,15 @@ def demand(self, val): @property def turn_off_all_heat(self) -> bool: - return self.hub.sensors.average_temp_outdoors.value > self.hub.options.heating_options.outdoor_temp_stop_heating + return ( + self.hub.sensors.average_temp_outdoors.value + > self.hub.options.heating_options.outdoor_temp_stop_heating + ) def _update_aux_offset_adjustments(self, max_lower: bool) -> None: - self._helpers.aux_offset_adjustments[OffsetAdjustments.PeakHour] = OFFSET_MIN_VALUE if max_lower else 0 + self._helpers.aux_offset_adjustments[OffsetAdjustments.PeakHour] = ( + OFFSET_MIN_VALUE if max_lower else 0 + ) self.current_adjusted_offset = OFFSET_MIN_VALUE async def async_adjusted_offset(self, current_offset: int) -> Tuple[int, bool]: @@ -73,7 +89,7 @@ async def async_adjusted_offset(self, current_offset: int) -> Tuple[int, bool]: ret = adjust_to_threshold( offsetdata, self.hub.sensors.average_temp_outdoors.value, - self.hub.offset.model.tolerance + self.hub.offset.model.tolerance, ) self.current_adjusted_offset = round(ret, 0) @@ -82,39 +98,46 @@ async def async_adjusted_offset(self, current_offset: int) -> Tuple[int, bool]: def _get_demand(self) -> Demand: return self._helpers.helper_get_demand() - def _current_tolerances(self, determinator: bool, current_offset: int, adjust_tolerances: bool = True) -> float: + def _current_tolerances( + self, determinator: bool, current_offset: int, adjust_tolerances: bool = True + ) -> float: _min, _max = self.hub.sensors.tolerances if adjust_tolerances: - tolerances = adjusted_tolerances( - current_offset, - _min, _max - ) + tolerances = adjusted_tolerances(current_offset, _min, _max) else: tolerances = _min, _max - return tolerances[0] if (determinator > 0 or determinator is True) else tolerances[1] + return ( + tolerances[0] + if (determinator > 0 or determinator is True) + else tolerances[1] + ) - async def async_calculated_offsetdata(self, current_offset: int) -> CalculatedOffsetModel: + async def async_calculated_offsetdata( + self, current_offset: int + ) -> CalculatedOffsetModel: tempdiff = get_tempdiff_inverted( - current_offset, - self.hub.sensors.get_tempdiff(), - self._current_tolerances + current_offset, self.hub.sensors.get_tempdiff(), self._current_tolerances ) tempextremas = get_temp_extremas( current_offset, - [self.hub.sensors.set_temp_indoors.adjusted_temp - t for t in - self.hub.sensors.average_temp_indoors.all_values], - self._current_tolerances + [ + self.hub.sensors.set_temp_indoors.adjusted_temp - t + for t in self.hub.sensors.average_temp_indoors.all_values + ], + self._current_tolerances, ) temptrend = get_temp_trend_offset( self.hub.sensors.temp_trend_indoors.is_clean, self.hub.sensors.predicted_temp, - self.hub.sensors.set_temp_indoors.adjusted_temp + self.hub.sensors.set_temp_indoors.adjusted_temp, ) - return CalculatedOffsetModel(current_offset=current_offset, - current_tempdiff=tempdiff, - current_temp_extremas=tempextremas, - current_temp_trend_offset=temptrend) + return CalculatedOffsetModel( + current_offset=current_offset, + current_tempdiff=tempdiff, + current_temp_extremas=tempextremas, + current_temp_trend_offset=temptrend, + ) async def async_update_operation(self): pass diff --git a/custom_components/peaqhvac/service/hvac/house_heater/house_heater_helpers.py b/custom_components/peaqhvac/service/hvac/house_heater/house_heater_helpers.py index fbf0cacf..e890babf 100644 --- a/custom_components/peaqhvac/service/hvac/house_heater/house_heater_helpers.py +++ b/custom_components/peaqhvac/service/hvac/house_heater/house_heater_helpers.py @@ -2,14 +2,22 @@ from datetime import datetime, timedelta from peaqevcore.common.wait_timer import WaitTimer -from custom_components.peaqhvac.service.hvac.const import WAITTIMER_TIMEOUT, HEATBOOST_TIMER -from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import CalculatedOffsetModel -from custom_components.peaqhvac.service.hvac.house_heater.models.offset_adjustments import OffsetAdjustments +from custom_components.peaqhvac.service.hvac.const import ( + WAITTIMER_TIMEOUT, + HEATBOOST_TIMER, +) +from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import ( + CalculatedOffsetModel, +) +from custom_components.peaqhvac.service.hvac.house_heater.models.offset_adjustments import ( + OffsetAdjustments, +) from custom_components.peaqhvac.service.models.enums.demand import Demand from custom_components.peaqhvac.service.models.enums.hvacmode import HvacMode _LOGGER = logging.getLogger(__name__) + class HouseHeaterHelpers: def __init__(self, hvac): self._hvac = hvac @@ -21,7 +29,7 @@ def __init__(self, hvac): OffsetAdjustments.TemporarilyLowerOffset: 0, OffsetAdjustments.PeakHour: 0, OffsetAdjustments.KeepCompressorRunning: 0, - OffsetAdjustments.LowerOffsetStrong: 0 + OffsetAdjustments.LowerOffsetStrong: 0, } def _lower_offset_addon(self) -> bool: @@ -36,7 +44,7 @@ def _lower_offset_threshold_breach(self) -> bool: if all( [ 30 <= datetime.now().minute < 58, - self._hvac.hub.sensors.peaqev_facade.above_stop_threshold + self._hvac.hub.sensors.peaqev_facade.above_stop_threshold, ] ): _LOGGER.debug("Lowering offset because of peak about to be breached.") @@ -64,17 +72,21 @@ def helper_get_demand(self) -> Demand: ) return Demand.ErrorDemand - def _keep_compressor_running(self, offsetdata: CalculatedOffsetModel, force_update: bool) -> bool: + def _keep_compressor_running( + self, offsetdata: CalculatedOffsetModel, force_update: bool + ) -> bool: """in certain conditions, up the offset to keep the compressor running for energy savings""" dm_zero_prediction = self._hvac.hub.sensors.dm_trend.predicted_time_at_value(0) now = datetime.now() if dm_zero_prediction is not None: - if all([ - self._hvac.hvac_mode is HvacMode.Heat, - self._hvac.compressor_frequency > 0, - self._hvac.hub.sensors.average_temp_outdoors.value < 0, - dm_zero_prediction < now + timedelta(hours=1) - ]): + if all( + [ + self._hvac.hvac_mode is HvacMode.Heat, + self._hvac.compressor_frequency > 0, + self._hvac.hub.sensors.average_temp_outdoors.value < 0, + dm_zero_prediction < now + timedelta(hours=1), + ] + ): offsetdata.current_offset += 1 force_update = True self.aux_offset_adjustments[OffsetAdjustments.KeepCompressorRunning] = 1 @@ -82,15 +94,25 @@ def _keep_compressor_running(self, offsetdata: CalculatedOffsetModel, force_upda self.aux_offset_adjustments[OffsetAdjustments.KeepCompressorRunning] = 0 return force_update - def temporarily_lower_offset(self, offsetdata: CalculatedOffsetModel, force_update: bool) -> bool: + def temporarily_lower_offset( + self, offsetdata: CalculatedOffsetModel, force_update: bool + ) -> bool: if self._wait_timer_breach.is_timeout(): if any([self._lower_offset_threshold_breach(), self._lower_offset_addon()]): - self.aux_offset_adjustments[OffsetAdjustments.TemporarilyLowerOffset] = -2 + self.aux_offset_adjustments[ + OffsetAdjustments.TemporarilyLowerOffset + ] = -2 offsetdata.current_offset -= 2 force_update = True elif self._hvac.hub.sensors.peaqev_installed: - if self._hvac.hvac_dm <= self._hvac.hub.options.heating_options.low_degree_minutes and self._hvac.hub.sensors.average_temp_outdoors.value > -10: - self.aux_offset_adjustments[OffsetAdjustments.TemporarilyLowerOffset] = -1 + if ( + self._hvac.hvac_dm + <= self._hvac.hub.options.heating_options.low_degree_minutes + and self._hvac.hub.sensors.average_temp_outdoors.value > -10 + ): + self.aux_offset_adjustments[ + OffsetAdjustments.TemporarilyLowerOffset + ] = -1 offsetdata.current_offset -= 1 force_update = True else: diff --git a/custom_components/peaqhvac/service/hvac/house_heater/models/calculated_offset.py b/custom_components/peaqhvac/service/hvac/house_heater/models/calculated_offset.py index 631df05d..bae629b1 100644 --- a/custom_components/peaqhvac/service/hvac/house_heater/models/calculated_offset.py +++ b/custom_components/peaqhvac/service/hvac/house_heater/models/calculated_offset.py @@ -3,6 +3,7 @@ _LOGGER = logging.getLogger(__name__) + @dataclass class CalculatedOffsetModel: current_offset: int diff --git a/custom_components/peaqhvac/service/hvac/house_heater/models/offset_adjustments.py b/custom_components/peaqhvac/service/hvac/house_heater/models/offset_adjustments.py index c6bc7eda..468373e2 100644 --- a/custom_components/peaqhvac/service/hvac/house_heater/models/offset_adjustments.py +++ b/custom_components/peaqhvac/service/hvac/house_heater/models/offset_adjustments.py @@ -1,5 +1,6 @@ from enum import Enum + class OffsetAdjustments(Enum): TemporarilyLowerOffset = 0 PeakHour = 1 diff --git a/custom_components/peaqhvac/service/hvac/house_heater/temperature_helper.py b/custom_components/peaqhvac/service/hvac/house_heater/temperature_helper.py index dc9f3b20..ddd3d329 100644 --- a/custom_components/peaqhvac/service/hvac/house_heater/temperature_helper.py +++ b/custom_components/peaqhvac/service/hvac/house_heater/temperature_helper.py @@ -5,7 +5,9 @@ _LOGGER = logging.getLogger(__name__) -def get_tempdiff_inverted(current_offset: int, temp_diff: float, determine_tolerance: callable) -> int: +def get_tempdiff_inverted( + current_offset: int, temp_diff: float, determine_tolerance: callable +) -> int: diff = temp_diff + 0.00001 if abs(diff) < 0.2: return 0 @@ -17,9 +19,13 @@ def get_tempdiff_inverted(current_offset: int, temp_diff: float, determine_toler return ret * -1 -def get_temp_extremas(current_offset: int, all_temps: list, determine_tolerance: callable) -> float: +def get_temp_extremas( + current_offset: int, all_temps: list, determine_tolerance: callable +) -> float: diffs = all_temps - cold_diffs, hot_diffs = [d for d in diffs if d > 0] + [0], [d for d in diffs if d < 0] + [0] + cold_diffs, hot_diffs = [d for d in diffs if d > 0] + [0], [ + d for d in diffs if d < 0 + ] + [0] hot_large = abs(min(hot_diffs)) cold_large = abs(max(cold_diffs)) if hot_large == cold_large: @@ -33,7 +39,9 @@ def get_temp_extremas(current_offset: int, all_temps: list, determine_tolerance: return ret / max(len(cold_diffs), 1) -def get_temp_trend_offset(temp_trend_is_clean: bool, predicted_temp: float, adjusted_temp: float) -> float: +def get_temp_trend_offset( + temp_trend_is_clean: bool, predicted_temp: float, adjusted_temp: float +) -> float: if not temp_trend_is_clean: return 0 new_temp_diff = round(predicted_temp - adjusted_temp, 3) diff --git a/custom_components/peaqhvac/service/hvac/house_ventilation.py b/custom_components/peaqhvac/service/hvac/house_ventilation.py index 6b0f2c7f..32e4ed84 100644 --- a/custom_components/peaqhvac/service/hvac/house_ventilation.py +++ b/custom_components/peaqhvac/service/hvac/house_ventilation.py @@ -5,7 +5,10 @@ from peaqevcore.common.models.observer_types import ObserverTypes from peaqevcore.models.hub.hubmember import HubMember -from custom_components.peaqhvac.service.hvac.const import WAITTIMER_TIMEOUT, WAITTIMER_VENT +from custom_components.peaqhvac.service.hvac.const import ( + WAITTIMER_TIMEOUT, + WAITTIMER_VENT, +) from peaqevcore.common.wait_timer import WaitTimer from custom_components.peaqhvac.service.models.enums.hvac_presets import HvacPresets from homeassistant.helpers.event import async_track_time_interval @@ -21,7 +24,11 @@ def __init__(self, hvac, observer): self._current_vent_state: bool = False self._latest_seen_fan_speed: float = 0 self._control_module: HubMember = HubMember(data_type=bool, initval=False) - async_track_time_interval(self._hvac.hub.state_machine, self.async_check_vent_boost, timedelta(seconds=30)) + async_track_time_interval( + self._hvac.hub.state_machine, + self.async_check_vent_boost, + timedelta(seconds=30), + ) @property def control_module(self) -> bool: @@ -47,7 +54,11 @@ def booster_update(self) -> bool: def _check_hvac_fan_speed(self) -> None: if self._hvac.fan_speed != self._latest_seen_fan_speed: - _LOGGER.debug("hvac ventilation speed changed from %s to %s", self._latest_seen_fan_speed, self._hvac.fan_speed) + _LOGGER.debug( + "hvac ventilation speed changed from %s to %s", + self._latest_seen_fan_speed, + self._hvac.fan_speed, + ) if self._latest_seen_fan_speed > self._hvac.fan_speed: """Decreased""" self._current_vent_state = False @@ -55,7 +66,10 @@ def _check_hvac_fan_speed(self) -> None: self._latest_seen_fan_speed = self._hvac.fan_speed async def async_check_vent_boost(self, caller=None) -> None: - if self._hvac.hub.sensors.temp_trend_indoors.samples > 0 and time.time() - self._wait_timer_boost.value > WAITTIMER_VENT: + if ( + self._hvac.hub.sensors.temp_trend_indoors.samples > 0 + and time.time() - self._wait_timer_boost.value > WAITTIMER_VENT + ): if self._vent_boost_warmth(): await self.async_vent_boost_start("Vent boosting because of warmth.") return @@ -63,49 +77,67 @@ async def async_check_vent_boost(self, caller=None) -> None: await self.async_vent_boost_start("Vent boost night cooling") return if self._vent_boost_low_dm(): - await self.async_vent_boost_start("Vent boosting because of low degree minutes.") + await self.async_vent_boost_start( + "Vent boosting because of low degree minutes." + ) return - if any([ - (self._hvac.hvac_dm > self._hvac.hub.options.heating_options.low_degree_minutes + 100 and self._hvac.hub.sensors.average_temp_outdoors.value < self._hvac.hub.options.heating_options.outdoor_temp_stop_heating), - self._hvac.hub.sensors.average_temp_outdoors.value < self._hvac.hub.options.heating_options.very_cold_temp - ]) and self.vent_boost: - _LOGGER.debug(f"recovered dm or very cold. stopping went boost. dm: {self._hvac.hvac_dm} > {self._hvac.hub.options.heating_options.low_degree_minutes + 100}, temp: {self._hvac.hub.sensors.average_temp_outdoors.value}") + if ( + any( + [ + ( + self._hvac.hvac_dm + > self._hvac.hub.options.heating_options.low_degree_minutes + + 100 + and self._hvac.hub.sensors.average_temp_outdoors.value + < self._hvac.hub.options.heating_options.outdoor_temp_stop_heating + ), + self._hvac.hub.sensors.average_temp_outdoors.value + < self._hvac.hub.options.heating_options.very_cold_temp, + ] + ) + and self.vent_boost + ): + _LOGGER.debug( + f"recovered dm or very cold. stopping went boost. dm: {self._hvac.hvac_dm} > {self._hvac.hub.options.heating_options.low_degree_minutes + 100}, temp: {self._hvac.hub.sensors.average_temp_outdoors.value}" + ) self.vent_boost = False await self.observer.async_broadcast(ObserverTypes.UpdateOperation) def _vent_boost_warmth(self) -> bool: return all( - [ - self._hvac.hub.sensors.get_tempdiff() > 4, - self._hvac.hub.sensors.get_tempdiff_in_out() > 5, - self._hvac.hub.sensors.temp_trend_indoors.gradient >= 0, - self._hvac.hub.sensors.temp_trend_outdoors.gradient >= 0, - datetime.now().hour in list(range(7, 21)), - self._hvac.hub.sensors.average_temp_outdoors.value >= self._hvac.hub.options.heating_options.outdoor_temp_stop_heating, - self._hvac.hub.sensors.set_temp_indoors.preset != HvacPresets.Away, - ] - ) + [ + self._hvac.hub.sensors.get_tempdiff() > 4, + self._hvac.hub.sensors.get_tempdiff_in_out() > 5, + self._hvac.hub.sensors.temp_trend_indoors.gradient >= 0, + self._hvac.hub.sensors.temp_trend_outdoors.gradient >= 0, + datetime.now().hour in list(range(7, 21)), + self._hvac.hub.sensors.average_temp_outdoors.value + >= self._hvac.hub.options.heating_options.outdoor_temp_stop_heating, + self._hvac.hub.sensors.set_temp_indoors.preset != HvacPresets.Away, + ] + ) def _vent_boost_night_cooling(self) -> bool: return all( - [ - self._hvac.hub.sensors.get_tempdiff() > 4, - self._hvac.hub.sensors.get_tempdiff_in_out() > 5, - self._hvac.hub.sensors.average_temp_outdoors.value >= self._hvac.hub.options.heating_options.outdoor_temp_stop_heating, - datetime.now().hour in list(range(21, 24)) + list(range(0, 7)), - self._hvac.hub.sensors.set_temp_indoors.preset != HvacPresets.Away, - ] - ) - - + [ + self._hvac.hub.sensors.get_tempdiff() > 4, + self._hvac.hub.sensors.get_tempdiff_in_out() > 5, + self._hvac.hub.sensors.average_temp_outdoors.value + >= self._hvac.hub.options.heating_options.outdoor_temp_stop_heating, + datetime.now().hour in list(range(21, 24)) + list(range(0, 7)), + self._hvac.hub.sensors.set_temp_indoors.preset != HvacPresets.Away, + ] + ) def _vent_boost_low_dm(self) -> bool: return all( - [ - self._hvac.hvac_dm <= self._hvac.hub.options.heating_options.low_degree_minutes, - self._hvac.hub.sensors.average_temp_outdoors.value >= self._hvac.hub.options.heating_options.very_cold_temp, - ] - ) + [ + self._hvac.hvac_dm + <= self._hvac.hub.options.heating_options.low_degree_minutes, + self._hvac.hub.sensors.average_temp_outdoors.value + >= self._hvac.hub.options.heating_options.very_cold_temp, + ] + ) async def async_vent_boost_start(self, msg) -> None: if not self.vent_boost and self.control_module: diff --git a/custom_components/peaqhvac/service/hvac/hvacfactory.py b/custom_components/peaqhvac/service/hvac/hvacfactory.py index e153b023..5d07bab7 100644 --- a/custom_components/peaqhvac/service/hvac/hvacfactory.py +++ b/custom_components/peaqhvac/service/hvac/hvacfactory.py @@ -5,8 +5,7 @@ from custom_components.peaqhvac.service.hvac.hvactypes.thermia import Thermia from custom_components.peaqhvac.service.hvac.interfaces.ihvactype import IHvacType from custom_components.peaqhvac.service.models.config_model import ConfigModel -from custom_components.peaqhvac.service.models.enums.hvacbrands import \ - HvacBrand +from custom_components.peaqhvac.service.models.enums.hvacbrands import HvacBrand class HvacFactory: @@ -14,4 +13,6 @@ class HvacFactory: @staticmethod def create(hass: HomeAssistant, options: ConfigModel, hub, observer) -> IHvacType: - return HvacFactory.HVACTYPES[options.hvacbrand](hass=hass, hub=hub, observer=observer) + return HvacFactory.HVACTYPES[options.hvacbrand]( + hass=hass, hub=hub, observer=observer + ) diff --git a/custom_components/peaqhvac/service/hvac/hvactypes/ivt.py b/custom_components/peaqhvac/service/hvac/hvactypes/ivt.py index 6a571892..05976d74 100644 --- a/custom_components/peaqhvac/service/hvac/hvactypes/ivt.py +++ b/custom_components/peaqhvac/service/hvac/hvactypes/ivt.py @@ -3,8 +3,9 @@ from custom_components.peaqhvac.service.hvac.interfaces.ihvactype import IHvacType from custom_components.peaqhvac.service.models.enums.hvacmode import HvacMode -from custom_components.peaqhvac.service.models.enums.hvacoperations import \ - HvacOperations +from custom_components.peaqhvac.service.models.enums.hvacoperations import ( + HvacOperations, +) from custom_components.peaqhvac.service.models.enums.sensortypes import SensorType _LOGGER = logging.getLogger(__name__) @@ -30,6 +31,6 @@ def get_sensor(self, sensor: SensorType = None): pass def _set_operation_call_parameters( - self, operation: HvacOperations, _value: any + self, operation: HvacOperations, _value: any ) -> Tuple[str, dict, str]: pass diff --git a/custom_components/peaqhvac/service/hvac/hvactypes/nibe.py b/custom_components/peaqhvac/service/hvac/hvactypes/nibe.py index d5e87ec7..a9326ed3 100644 --- a/custom_components/peaqhvac/service/hvac/hvactypes/nibe.py +++ b/custom_components/peaqhvac/service/hvac/hvactypes/nibe.py @@ -3,15 +3,17 @@ from custom_components.peaqhvac.service.hvac.interfaces.ihvactype import IHvacType from custom_components.peaqhvac.service.models.enums.hvacmode import HvacMode -from custom_components.peaqhvac.service.models.enums.hvacoperations import \ - HvacOperations -from custom_components.peaqhvac.service.models.enums.sensortypes import \ - SensorType +from custom_components.peaqhvac.service.models.enums.hvacoperations import ( + HvacOperations, +) +from custom_components.peaqhvac.service.models.enums.sensortypes import SensorType _LOGGER = logging.getLogger(__name__) NIBE_MAX_THRESHOLD = 10 NIBE_MIN_THRESHOLD = -10 + + class Nibe(IHvacType): domain = "Nibe" water_heater_entity = None @@ -58,7 +60,11 @@ def delta_return_temp(self): try: temp = self.get_sensor(SensorType.HvacTemp) returntemp = self.get_sensor(SensorType.HotWaterReturn) - return round(float(self._handle_sensor(temp)) - float(self._handle_sensor(returntemp)),2,) + return round( + float(self._handle_sensor(temp)) + - float(self._handle_sensor(returntemp)), + 2, + ) except TypeError as e: _LOGGER.debug(f"Unable to calculate delta return: {e}") return 0 @@ -123,7 +129,9 @@ def _service_domain_per_operation(self, operation: HvacOperations) -> str: return "switch" raise ValueError(f"Operation {operation} not supported") - def _transform_servicecall_value(self, value: any, operation: HvacOperations) -> any: + def _transform_servicecall_value( + self, value: any, operation: HvacOperations + ) -> any: match operation: case HvacOperations.Offset: return "set_value" @@ -136,7 +144,9 @@ def _set_servicecall_params(self, operation, _value): ret["value"] = self._cap_nibe_offset_value(_value) return ret - def _set_operation_call_parameters(self, operation: HvacOperations, _value: any) -> Tuple[str, dict, str]: + def _set_operation_call_parameters( + self, operation: HvacOperations, _value: any + ) -> Tuple[str, dict, str]: call_operation = self._transform_servicecall_value(_value, operation) service_domain = self._service_domain_per_operation(operation) params = self._set_servicecall_params(operation, _value) diff --git a/custom_components/peaqhvac/service/hvac/hvactypes/thermia.py b/custom_components/peaqhvac/service/hvac/hvactypes/thermia.py index f6b94159..962a2384 100644 --- a/custom_components/peaqhvac/service/hvac/hvactypes/thermia.py +++ b/custom_components/peaqhvac/service/hvac/hvactypes/thermia.py @@ -3,8 +3,9 @@ from custom_components.peaqhvac.service.hvac.interfaces.ihvactype import IHvacType from custom_components.peaqhvac.service.models.enums.hvacmode import HvacMode -from custom_components.peaqhvac.service.models.enums.hvacoperations import \ - HvacOperations +from custom_components.peaqhvac.service.models.enums.hvacoperations import ( + HvacOperations, +) from custom_components.peaqhvac.service.models.enums.sensortypes import SensorType _LOGGER = logging.getLogger(__name__) @@ -30,6 +31,6 @@ def get_sensor(self, sensor: SensorType = None): pass def _set_operation_call_parameters( - self, operation: HvacOperations, _value: any + self, operation: HvacOperations, _value: any ) -> Tuple[str, dict, str]: - pass \ No newline at end of file + pass diff --git a/custom_components/peaqhvac/service/hvac/interfaces/iheater.py b/custom_components/peaqhvac/service/hvac/interfaces/iheater.py index a857f88a..ae03d196 100644 --- a/custom_components/peaqhvac/service/hvac/interfaces/iheater.py +++ b/custom_components/peaqhvac/service/hvac/interfaces/iheater.py @@ -9,6 +9,8 @@ _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL = 60 + + class IHeater(ABC): def __init__(self, hub): diff --git a/custom_components/peaqhvac/service/hvac/interfaces/ihvactype.py b/custom_components/peaqhvac/service/hvac/interfaces/ihvactype.py index afb78497..515e1716 100644 --- a/custom_components/peaqhvac/service/hvac/interfaces/ihvactype.py +++ b/custom_components/peaqhvac/service/hvac/interfaces/ihvactype.py @@ -8,7 +8,9 @@ from peaqevcore.common.models.observer_types import ObserverTypes -from custom_components.peaqhvac.service.hvac.water_heater.cycle_waterboost import async_cycle_waterboost +from custom_components.peaqhvac.service.hvac.water_heater.cycle_waterboost import ( + async_cycle_waterboost, +) from custom_components.peaqhvac.service.observer.iobserver_coordinator import IObserver if TYPE_CHECKING: @@ -17,11 +19,17 @@ from homeassistant.core import HomeAssistant import custom_components.peaqhvac.extensionmethods as ex -from custom_components.peaqhvac.service.hvac.house_heater.house_heater_coordinator import HouseHeaterCoordinator -from custom_components.peaqhvac.service.hvac.water_heater.water_heater_coordinator import WaterHeater +from custom_components.peaqhvac.service.hvac.house_heater.house_heater_coordinator import ( + HouseHeaterCoordinator, +) +from custom_components.peaqhvac.service.hvac.water_heater.water_heater_coordinator import ( + WaterHeater, +) from custom_components.peaqhvac.service.hvac.house_ventilation import HouseVentilation from custom_components.peaqhvac.service.models.enums.hvacmode import HvacMode -from custom_components.peaqhvac.service.models.enums.hvacoperations import HvacOperations +from custom_components.peaqhvac.service.models.enums.hvacoperations import ( + HvacOperations, +) from custom_components.peaqhvac.service.models.enums.sensortypes import SensorType from custom_components.peaqhvac.service.models.ihvac_model import IHvacModel @@ -29,7 +37,7 @@ UPDATE_INTERVALS = { - HvacOperations.Offset: 300, + HvacOperations.Offset: 300, HvacOperations.VentBoost: 1800, } @@ -38,7 +46,7 @@ class IHvacType: _force_update: bool = False update_list: dict[HvacOperations, any] = {} periodic_update_timers: dict = { - HvacOperations.Offset: 0, + HvacOperations.Offset: 0, HvacOperations.VentBoost: 0, } @@ -77,7 +85,7 @@ def get_sensor(self, sensor: SensorType = None): @abstractmethod def _set_operation_call_parameters( - self, operation: HvacOperations, _value: any + self, operation: HvacOperations, _value: any ) -> Tuple[str, dict, str]: pass @@ -102,10 +110,10 @@ def compressor_frequency(self) -> int: @property def hvac_electrical_addon(self) -> bool: value_conversion = { - "Alarm": False, + "Alarm": False, "Blocked": False, - "Off": False, - "Active": True, + "Off": False, + "Active": True, } ret = self.get_value(SensorType.ElectricalAddition, str) return value_conversion.get(ret, False) @@ -133,7 +141,9 @@ async def async_update_offset(self) -> bool: return ret try: _hvac_offset = self.hvac_offset - new_offset, force_update = await self.house_heater.async_adjusted_offset(self.model.current_offset) + new_offset, force_update = await self.house_heater.async_adjusted_offset( + self.model.current_offset + ) if new_offset != self.model.current_offset: self.model.current_offset = new_offset self._force_update = force_update @@ -193,7 +203,9 @@ async def async_update_ventilation(self) -> None: if await self.async_ready_to_update(HvacOperations.VentBoost): _vent_state = int(self.house_ventilation.vent_boost) if _vent_state != self.update_list.get(HvacOperations.VentBoost, None): - _LOGGER.debug(f"Vent boost state changed to {_vent_state}. Adding to update list.") + _LOGGER.debug( + f"Vent boost state changed to {_vent_state}. Adding to update list." + ) self.update_list[HvacOperations.VentBoost] = _vent_state async def async_update_heat(self) -> None: @@ -205,7 +217,8 @@ async def async_boost_water(self, target_temp: float) -> None: if self.hub.hvac_service.water_heater.control_module: _LOGGER.debug(f"init water boost process") self.hub.state_machine.async_create_task( - async_cycle_waterboost(target_temp, self.async_update_system, self.hub)) + async_cycle_waterboost(target_temp, self.async_update_system, self.hub) + ) _LOGGER.debug(f"return from water boost process") async def async_perform_periodic_updates(self) -> None: @@ -218,7 +231,9 @@ async def async_perform_periodic_updates(self) -> None: for r in removelist: self.update_list.pop(r) - async def async_update_system(self, operation: HvacOperations, set_val: any = None) -> bool: + async def async_update_system( + self, operation: HvacOperations, set_val: any = None + ) -> bool: if self.hub.sensors.peaqhvac_enabled.value: _value = set_val if self.hub.sensors.average_temp_outdoors.initialized_percentage > 0.5: @@ -236,7 +251,10 @@ async def async_update_system(self, operation: HvacOperations, set_val: any = No return False def timer_timeout(self, operation) -> bool: - return time.time() - self.periodic_update_timers[operation] > UPDATE_INTERVALS[operation] + return ( + time.time() - self.periodic_update_timers[operation] + > UPDATE_INTERVALS[operation] + ) async def async_ready_to_update(self, operation) -> bool: match operation: diff --git a/custom_components/peaqhvac/service/hvac/offset/offset_cache.py b/custom_components/peaqhvac/service/hvac/offset/offset_cache.py index f1f0c353..676698fb 100644 --- a/custom_components/peaqhvac/service/hvac/offset/offset_cache.py +++ b/custom_components/peaqhvac/service/hvac/offset/offset_cache.py @@ -40,7 +40,12 @@ def get_cache_for_today(dt: date, prices: list) -> CacheDict: return None -def update_cache(list_dt: date, prices: List[float], offsets: dict[int, float], now_dt: datetime = datetime.now()): +def update_cache( + list_dt: date, + prices: List[float], + offsets: dict[int, float], + now_dt: datetime = datetime.now(), +): global _offsetCache if len(prices) < 1 or len(offsets) < 1: return @@ -62,4 +67,6 @@ def update_cache(list_dt: date, prices: List[float], offsets: dict[int, float], for h in _offsetCache: if h.dt != now_dt: h.today = False - _offsetCache = [h for h in _offsetCache if h.dt >= now_dt.date() - timedelta(days=2)] + _offsetCache = [ + h for h in _offsetCache if h.dt >= now_dt.date() - timedelta(days=2) + ] diff --git a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator.py b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator.py index 9a00fb7b..53afe4ba 100644 --- a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator.py +++ b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator.py @@ -7,9 +7,14 @@ from peaqevcore.services.hourselection.hoursselection import Hoursselection import custom_components.peaqhvac.service.hvac.offset.offset_cache as cache from custom_components.peaqhvac.service.hvac.offset.offset_utils import ( - max_price_lower_internal, offset_per_day, set_offset_dict) + max_price_lower_internal, + offset_per_day, + set_offset_dict, +) from custom_components.peaqhvac.service.hvac.offset.peakfinder import ( - identify_peaks, smooth_transitions) + identify_peaks, + smooth_transitions, +) from custom_components.peaqhvac.service.models.offset_model import OffsetModel from custom_components.peaqhvac.service.observer.iobserver_coordinator import IObserver @@ -19,7 +24,8 @@ class OffsetCoordinator: """The class that provides the offsets for the hvac""" - def __init__(self, hub, observer: IObserver, hours_type: Hoursselection = None): #type: ignore + + def __init__(self, hub, observer: IObserver, hours_type: Hoursselection = None): # type: ignore self._hub = hub self.observer = observer self.model = OffsetModel(hub) @@ -51,13 +57,17 @@ def current_offset(self) -> int: try: self._set_offset() if len(self.model.raw_offsets): - ret = self.model.raw_offsets.get(datetime.now().replace(minute=0, second=0, microsecond=0), 0) + ret = self.model.raw_offsets.get( + datetime.now().replace(minute=0, second=0, microsecond=0), 0 + ) except KeyError as e: - _LOGGER.error(f"Unable to get current offset: {e}. raw_offsets: {self.model.raw_offsets}") + _LOGGER.error( + f"Unable to get current offset: {e}. raw_offsets: {self.model.raw_offsets}" + ) finally: return ret - #self.model.current_offset_dict_combined = ret.calculated_offsets + # self.model.current_offset_dict_combined = ret.calculated_offsets def _update_prognosis(self) -> None: self.model.prognosis = self._hub.prognosis.prognosis @@ -73,10 +83,14 @@ def max_price_lower(self, tempdiff: float) -> bool: def _update_offset(self, weather_adjusted_today: dict | None = None) -> dict: cached_today = cache.get_cache_for_today(datetime.now().date(), self.prices) - cached_tomorrow = cache.get_cache_for_today((datetime.now() + timedelta(days=1)).date(), self.prices_tomorrow) - cached_midnight_problem = cache.get_cache_for_today(datetime.now().date() - timedelta(days=1), self.prices) - - #todo: re-add caching + cached_tomorrow = cache.get_cache_for_today( + (datetime.now() + timedelta(days=1)).date(), self.prices_tomorrow + ) + cached_midnight_problem = cache.get_cache_for_today( + datetime.now().date() - timedelta(days=1), self.prices + ) + + # todo: re-add caching try: # if all([cached_today, cached_tomorrow]): # #_LOGGER.debug("Using cached values for today and tomorrow") @@ -102,9 +116,19 @@ def _update_offset(self, weather_adjusted_today: dict | None = None) -> dict: # cache.update_cache(datetime.now().date(), self.prices, today_values) # cache.update_cache((datetime.now() + timedelta(days=1)).date(), self.prices_tomorrow, tomorrow_values) - all_values = set_offset_dict(self.prices+self.prices_tomorrow, datetime.now(), self.min_price) - _LOGGER.debug("all_values", all_values, self.prices, self.prices_tomorrow, self.min_price) - offsets_per_day = self._calculate_offset_per_day(all_values, weather_adjusted_today) + all_values = set_offset_dict( + self.prices + self.prices_tomorrow, datetime.now(), self.min_price + ) + _LOGGER.debug( + "all_values", + all_values, + self.prices, + self.prices_tomorrow, + self.min_price, + ) + offsets_per_day = self._calculate_offset_per_day( + all_values, weather_adjusted_today + ) tolerance = self.model.tolerance if self.model.tolerance is not None else 3 for k, v in offsets_per_day.items(): if v > tolerance: @@ -121,11 +145,13 @@ def _update_offset(self, weather_adjusted_today: dict | None = None) -> dict: _LOGGER.exception(f"Exception while trying to calculate offset: {e}") return {} - def _calculate_offset_per_day(self, day_values: dict, weather_adjusted_today: dict | None = None) -> dict: + def _calculate_offset_per_day( + self, day_values: dict, weather_adjusted_today: dict | None = None + ) -> dict: if weather_adjusted_today is None: indoors_preset = self._hub.sensors.set_temp_indoors.preset return offset_per_day( - all_prices=self.prices+self.prices_tomorrow, + all_prices=self.prices + self.prices_tomorrow, day_values=day_values, tolerance=self.model.tolerance, indoors_preset=indoors_preset, @@ -139,11 +165,15 @@ def _set_offset(self) -> None: self.model.calculated_offsets = self.model.raw_offsets if len(self._hub.prognosis.prognosis) > 0: try: - _weather_dict = self._hub.prognosis.get_weatherprognosis_adjustment(self.model.raw_offsets) + _weather_dict = self._hub.prognosis.get_weatherprognosis_adjustment( + self.model.raw_offsets + ) _LOGGER.debug("weather-prognosis", _weather_dict) if len(_weather_dict.items()) > 0: _LOGGER.debug("weather-prognosis", _weather_dict) - self.model.calculated_offsets = self._update_offset(_weather_dict) + self.model.calculated_offsets = self._update_offset( + _weather_dict + ) except Exception as e: _LOGGER.warning( f"Unable to calculate prognosis-offsets. Setting normal calculation: {e}" @@ -153,15 +183,10 @@ def _set_offset(self) -> None: self.observer.broadcast(ObserverTypes.OffsetRecalculation) else: if self._hub.is_initialized: - _LOGGER.warning(f"Hub is ready but I'm unable to set offset. Prices num:{len(self.prices) if self.prices else 0}") + _LOGGER.warning( + f"Hub is ready but I'm unable to set offset. Prices num:{len(self.prices) if self.prices else 0}" + ) def _update_model(self) -> None: self.model.peaks_today = identify_peaks(self.prices) self.model.peaks_tomorrow = identify_peaks(self.prices_tomorrow) - - - - - - - diff --git a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_factory.py b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_factory.py index 393dbf2a..83df5588 100644 --- a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_factory.py +++ b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_factory.py @@ -4,13 +4,19 @@ from peaqevcore.services.hourselection.hoursselection import Hoursselection -from custom_components.peaqhvac.service.hvac.offset.offset_coordinator_peaqev import OffsetCoordinatorPeaqEv -from custom_components.peaqhvac.service.hvac.offset.offset_coordinator_standalone import OffsetCoordinatorStandAlone +from custom_components.peaqhvac.service.hvac.offset.offset_coordinator_peaqev import ( + OffsetCoordinatorPeaqEv, +) +from custom_components.peaqhvac.service.hvac.offset.offset_coordinator_standalone import ( + OffsetCoordinatorStandAlone, +) from custom_components.peaqhvac.service.observer.iobserver_coordinator import IObserver if TYPE_CHECKING: from custom_components.peaqhvac.service.hub.hub import Hub -from custom_components.peaqhvac.service.hvac.offset.offset_coordinator import OffsetCoordinator +from custom_components.peaqhvac.service.hvac.offset.offset_coordinator import ( + OffsetCoordinator, +) class OffsetFactory: @@ -20,4 +26,3 @@ def create(hub: Hub, observer: IObserver) -> OffsetCoordinator: if hub.options.misc_options.peaqev_discovered: return OffsetCoordinatorPeaqEv(hub, observer, None) return OffsetCoordinatorStandAlone(hub, observer, Hoursselection()) - diff --git a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_peaqev.py b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_peaqev.py index a7ee3b68..3ce1a4dc 100644 --- a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_peaqev.py +++ b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_peaqev.py @@ -4,22 +4,34 @@ from peaqevcore.common.models.observer_types import ObserverTypes from peaqevcore.services.hourselection.hoursselection import Hoursselection -from custom_components.peaqhvac.service.hvac.offset.offset_coordinator import OffsetCoordinator +from custom_components.peaqhvac.service.hvac.offset.offset_coordinator import ( + OffsetCoordinator, +) from custom_components.peaqhvac.service.observer.iobserver_coordinator import IObserver _LOGGER = logging.getLogger(__name__) + class OffsetCoordinatorPeaqEv(OffsetCoordinator): """The class that provides the offsets for the hvac with peaqev installed""" - def __init__(self, hub, observer: IObserver, hours_type: Hoursselection = None): #type: ignore + def __init__(self, hub, observer: IObserver, hours_type: Hoursselection = None): # type: ignore _LOGGER.debug("found peaqev and will not init hourselection") super().__init__(hub, observer, hours_type) self._prices = None self._prices_tomorrow = None - self._update_prices([hub.sensors.peaqev_facade.hours.prices, hub.sensors.peaqev_facade.hours.prices_tomorrow]) - hub.sensors.peaqev_facade.peaqev_observer.add(ObserverTypes.PricesChanged, self.async_update_prices_blank) - hub.sensors.peaqev_facade.peaqev_observer.add(ObserverTypes.SpotpriceInitialized, self.async_update_prices_blank) + self._update_prices( + [ + hub.sensors.peaqev_facade.hours.prices, + hub.sensors.peaqev_facade.hours.prices_tomorrow, + ] + ) + hub.sensors.peaqev_facade.peaqev_observer.add( + ObserverTypes.PricesChanged, self.async_update_prices_blank + ) + hub.sensors.peaqev_facade.peaqev_observer.add( + ObserverTypes.SpotpriceInitialized, self.async_update_prices_blank + ) @property def prices(self) -> list: @@ -41,7 +53,12 @@ async def async_update_prices(self, prices) -> None: self._update_prices(prices) async def async_update_prices_blank(self) -> None: - self._update_prices([self._hub.sensors.peaqev_facade.hours.prices, self._hub.sensors.peaqev_facade.hours.prices_tomorrow]) + self._update_prices( + [ + self._hub.sensors.peaqev_facade.hours.prices, + self._hub.sensors.peaqev_facade.hours.prices_tomorrow, + ] + ) def _update_prices(self, prices) -> None: _LOGGER.debug("Updating prices") @@ -50,4 +67,4 @@ def _update_prices(self, prices) -> None: if self._prices_tomorrow != prices[1]: self._prices_tomorrow = prices[1] self._set_offset() - self._update_model() \ No newline at end of file + self._update_model() diff --git a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_standalone.py b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_standalone.py index 1a6ed840..bd31ce6a 100644 --- a/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_standalone.py +++ b/custom_components/peaqhvac/service/hvac/offset/offset_coordinator_standalone.py @@ -5,12 +5,15 @@ from peaqevcore.common.models.observer_types import ObserverTypes from peaqevcore.services.hourselection.hoursselection import Hoursselection -from custom_components.peaqhvac.service.hvac.offset.offset_coordinator import OffsetCoordinator +from custom_components.peaqhvac.service.hvac.offset.offset_coordinator import ( + OffsetCoordinator, +) from custom_components.peaqhvac.service.observer.iobserver_coordinator import IObserver _LOGGER = logging.getLogger(__name__) + class OffsetCoordinatorStandAlone(OffsetCoordinator): """The class that provides the offsets for the hvac with peaqev installed""" @@ -34,6 +37,8 @@ def min_price(self) -> float: async def async_update_prices(self, prices) -> None: await self.hours.async_update_prices(prices[0], prices[1]) - _LOGGER.debug(f"Updated prices to {self.hours.prices, self.hours.prices_tomorrow}") + _LOGGER.debug( + f"Updated prices to {self.hours.prices, self.hours.prices_tomorrow}" + ) self._set_offset() self._update_model() diff --git a/custom_components/peaqhvac/service/hvac/offset/offset_utils.py b/custom_components/peaqhvac/service/hvac/offset/offset_utils.py index 1aaaa0b5..c5c5f30f 100644 --- a/custom_components/peaqhvac/service/hvac/offset/offset_utils.py +++ b/custom_components/peaqhvac/service/hvac/offset/offset_utils.py @@ -2,9 +2,10 @@ from datetime import datetime, timedelta from statistics import mean, stdev -from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import CalculatedOffsetModel -from custom_components.peaqhvac.service.models.enums.hvac_presets import \ - HvacPresets +from custom_components.peaqhvac.service.hvac.house_heater.models.calculated_offset import ( + CalculatedOffsetModel, +) +from custom_components.peaqhvac.service.models.enums.hvac_presets import HvacPresets _LOGGER = logging.getLogger(__name__) @@ -29,10 +30,10 @@ def flat_day_lower_tolerance(prices): def offset_per_day( - day_values: dict, - all_prices: list[float], - tolerance: int | None, - indoors_preset: HvacPresets = HvacPresets.Normal, + day_values: dict, + all_prices: list[float], + tolerance: int | None, + indoors_preset: HvacPresets = HvacPresets.Normal, ) -> dict: ret = {} if tolerance is not None: @@ -49,7 +50,7 @@ def offset_per_day( def get_offset_dict(offset_dict, dt_now) -> dict: return { - TODAY: offset_dict.get(dt_now.date(), {}), + TODAY: offset_dict.get(dt_now.date(), {}), TOMORROW: offset_dict.get(dt_now.date() + timedelta(days=1), {}), } @@ -69,7 +70,9 @@ def _get_timedelta(prices: list[float]) -> int: return 15 -def _deviation_from_mean(prices: list[float], min_price: float, dt: datetime) -> dict[datetime, float]: +def _deviation_from_mean( + prices: list[float], min_price: float, dt: datetime +) -> dict[datetime, float]: if not len(prices): return {} delta = _get_timedelta(prices) @@ -114,7 +117,9 @@ def max_price_lower_internal(tempdiff: float, peaks_today: list) -> bool: return False -def adjust_to_threshold(offsetdata: CalculatedOffsetModel, outdoors_value: int, tolerance: int) -> int: +def adjust_to_threshold( + offsetdata: CalculatedOffsetModel, outdoors_value: int, tolerance: int +) -> int: adjustment = offsetdata.sum_values() if adjustment is None or outdoors_value > 17: return min(0, adjustment) diff --git a/custom_components/peaqhvac/service/hvac/offset/peakfinder.py b/custom_components/peaqhvac/service/hvac/offset/peakfinder.py index 2474c693..b0fa7ebd 100644 --- a/custom_components/peaqhvac/service/hvac/offset/peakfinder.py +++ b/custom_components/peaqhvac/service/hvac/offset/peakfinder.py @@ -15,10 +15,10 @@ def identify_peaks(prices: list) -> list[int]: ret.append(idx) else: if all( - [ - _check_deviation_peaks(p, prices[idx - 1]), - _check_deviation_peaks(p, prices[idx + 1]), - ] + [ + _check_deviation_peaks(p, prices[idx - 1]), + _check_deviation_peaks(p, prices[idx + 1]), + ] ): ret.append(idx) return ret @@ -34,10 +34,10 @@ def identify_valleys(prices: list) -> list[int]: ret.append(idx) else: if all( - [ - _check_deviation_valleys(p, prices[idx - 1]), - _check_deviation_valleys(p, prices[idx + 1]), - ] + [ + _check_deviation_valleys(p, prices[idx - 1]), + _check_deviation_valleys(p, prices[idx + 1]), + ] ): ret.append(idx) return ret @@ -68,13 +68,13 @@ def find_single_valleys(prices: list) -> list[int]: pass else: if all( - [ - p < prices[idx - 1], - p < prices[idx + 1], - min(prices[idx - 1], prices[idx + 1]) - / max(prices[idx - 1], prices[idx + 1]) - > 0.8, - ] + [ + p < prices[idx - 1], + p < prices[idx + 1], + min(prices[idx - 1], prices[idx + 1]) + / max(prices[idx - 1], prices[idx + 1]) + > 0.8, + ] ): ret.append(idx) return ret diff --git a/custom_components/peaqhvac/service/hvac/water_heater/const.py b/custom_components/peaqhvac/service/hvac/water_heater/const.py index 8f0efe5f..e5098428 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/const.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/const.py @@ -1,4 +1,3 @@ - WAITTIMER_TIMEOUT = 1800 LOWTEMP_THRESHOLD = 30 HIGHTEMP_THRESHOLD = 40 diff --git a/custom_components/peaqhvac/service/hvac/water_heater/cycle_waterboost.py b/custom_components/peaqhvac/service/hvac/water_heater/cycle_waterboost.py index 9e6e01a6..e0d82cf9 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/cycle_waterboost.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/cycle_waterboost.py @@ -3,20 +3,31 @@ import asyncio from datetime import datetime -from custom_components.peaqhvac.service.models.enums.hvacoperations import HvacOperations +from custom_components.peaqhvac.service.models.enums.hvacoperations import ( + HvacOperations, +) _LOGGER = logging.getLogger(__name__) -async def async_cycle_waterboost(target_temp: float, async_update_system: callable, hub) -> bool: +async def async_cycle_waterboost( + target_temp: float, async_update_system: callable, hub +) -> bool: end_time = time.time() + 1800 await async_update_system(operation=HvacOperations.WaterBoost, set_val=1) - while all([ - time.time() < end_time, - hub.hvac_service.water_heater.current_temperature < target_temp - ]): + while all( + [ + time.time() < end_time, + hub.hvac_service.water_heater.current_temperature < target_temp, + ] + ): if hub.sensors.peaqev_installed: - if all([hub.sensors.peaqev_facade.above_stop_threshold, 20 <= datetime.now().minute < 55]): + if all( + [ + hub.sensors.peaqev_facade.above_stop_threshold, + 20 <= datetime.now().minute < 55, + ] + ): _LOGGER.debug("Peak is being breached. Turning off water heating") break await asyncio.sleep(5) diff --git a/custom_components/peaqhvac/service/hvac/water_heater/models/group.py b/custom_components/peaqhvac/service/hvac/water_heater/models/group.py index 22eb0b3b..d67ab814 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/models/group.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/models/group.py @@ -4,4 +4,4 @@ class Group: def __init__(self, group_type: GroupType, hours: list[int]): self.group_type = group_type - self.hours = hours \ No newline at end of file + self.hours = hours diff --git a/custom_components/peaqhvac/service/hvac/water_heater/models/next_water_boost_model.py b/custom_components/peaqhvac/service/hvac/water_heater/models/next_water_boost_model.py index 16d1db88..36a6120b 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/models/next_water_boost_model.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/models/next_water_boost_model.py @@ -8,7 +8,6 @@ from custom_components.peaqhvac.service.models.enums.hvac_presets import HvacPresets - HOUR_LIMIT = 18 DELAY_LIMIT = 48 MIN_DEMAND = 26 @@ -16,26 +15,26 @@ DEMAND_MINUTES = { HvacPresets.Normal: { - Demand.ErrorDemand: 0, - Demand.NoDemand: 0, - Demand.LowDemand: 20, + Demand.ErrorDemand: 0, + Demand.NoDemand: 0, + Demand.LowDemand: 20, Demand.MediumDemand: 26, - Demand.HighDemand: 32 + Demand.HighDemand: 32, }, - HvacPresets.Eco: { - Demand.ErrorDemand: 0, - Demand.NoDemand: 0, - Demand.LowDemand: 20, + HvacPresets.Eco: { + Demand.ErrorDemand: 0, + Demand.NoDemand: 0, + Demand.LowDemand: 20, Demand.MediumDemand: 24, - Demand.HighDemand: 28 + Demand.HighDemand: 28, }, - HvacPresets.Away: { - Demand.ErrorDemand: 0, - Demand.NoDemand: 0, - Demand.LowDemand: 0, + HvacPresets.Away: { + Demand.ErrorDemand: 0, + Demand.NoDemand: 0, + Demand.LowDemand: 0, Demand.MediumDemand: 20, - Demand.HighDemand: 20 - } + Demand.HighDemand: 20, + }, } @@ -54,16 +53,22 @@ def get_demand(temp) -> Demand: return Demand.HighDemand return Demand.ErrorDemand + from dataclasses import dataclass, field from datetime import datetime, timedelta + @dataclass class NextWaterBoostModel: min_price: float = None # type: ignore - non_hours_raw: list[int] = field(default_factory=lambda: [], repr=False, compare=False) - demand_hours_raw: list[int] = field(default_factory=lambda: [], repr=False, compare=False) + non_hours_raw: list[int] = field( + default_factory=lambda: [], repr=False, compare=False + ) + demand_hours_raw: list[int] = field( + default_factory=lambda: [], repr=False, compare=False + ) initialized: bool = False - price_dict:dict = field(default_factory=lambda: {}) + price_dict: dict = field(default_factory=lambda: {}) preset: HvacPresets = HvacPresets.Normal _now_dt: datetime = None # type: ignore latest_boost: datetime = None # type: ignore @@ -82,8 +87,10 @@ class NextWaterBoostModel: def __post_init__(self): self._now_dt = datetime.now() if self.now_dt is None else self.now_dt - self.latest_boost = self.now_dt if self.latest_boost is None else self.latest_boost - self.min_price = -float('inf') if self.min_price is None else self.min_price + self.latest_boost = ( + self.now_dt if self.latest_boost is None else self.latest_boost + ) + self.min_price = -float("inf") if self.min_price is None else self.min_price @property def cold_limit(self) -> datetime: @@ -113,35 +120,67 @@ def now_dt(self) -> datetime: def _create_price_dict(self, prices) -> dict: startofday = self.now_dt.replace(hour=0, minute=0) - return {startofday + timedelta(hours=i): prices[i] for i in range(0, len(prices))} - - def update(self, temp, temp_trend, target_temp, prices_today: list, prices_tomorrow: list, preset: HvacPresets, - now_dt=None, latest_boost: datetime = None) -> None: + return { + startofday + timedelta(hours=i): prices[i] for i in range(0, len(prices)) + } + + def update( + self, + temp, + temp_trend, + target_temp, + prices_today: list, + prices_tomorrow: list, + preset: HvacPresets, + now_dt=None, + latest_boost: datetime = None, + ) -> None: _old_dt = self.now_dt self.set_now_dt(now_dt) new_price_dict = self._create_price_dict(prices_today + prices_tomorrow) if new_price_dict != self.price_dict: - if all([ - any([k for k in new_price_dict.keys() if k.date() != self.now_dt.date()]), - not any([k for k in self.price_dict.keys() if k.date() != self.now_dt.date()]) - ]): + if all( + [ + any( + [ + k + for k in new_price_dict.keys() + if k.date() != self.now_dt.date() + ] + ), + not any( + [ + k + for k in self.price_dict.keys() + if k.date() != self.now_dt.date() + ] + ), + ] + ): self.latest_calculation = None self.price_dict = new_price_dict self.should_update = True new_non_hours = self._set_hours(self.non_hours_raw, preset) new_demand_hours = self._set_hours(self.demand_hours_raw, preset) - new_temp_trend = DEFAULT_TEMP_TREND if temp_trend > DEFAULT_TEMP_TREND else temp_trend - - if any([ - _old_dt.hour != self.now_dt.hour, - self.latest_boost != latest_boost, - self.non_hours != new_non_hours, - self.demand_hours != new_demand_hours, - self.preset != preset, - self.temp_trend != new_temp_trend, - self.current_temp != temp, - self.target_temp != target_temp - ]) and not self.should_update: + new_temp_trend = ( + DEFAULT_TEMP_TREND if temp_trend > DEFAULT_TEMP_TREND else temp_trend + ) + + if ( + any( + [ + _old_dt.hour != self.now_dt.hour, + self.latest_boost != latest_boost, + self.non_hours != new_non_hours, + self.demand_hours != new_demand_hours, + self.preset != preset, + self.temp_trend != new_temp_trend, + self.current_temp != temp, + self.target_temp != target_temp, + ] + ) + and not self.should_update + ): self.should_update = True self.latest_boost = latest_boost @@ -166,4 +205,6 @@ def set_now_dt(self, now_dt=None) -> None: self._now_dt = datetime.now() if now_dt is None else now_dt def set_floating_mean(self, now_dt=None) -> None: - self.floating_mean = mean([v for k,v in self.price_dict.items() if k >=self.now_dt])*0.9 \ No newline at end of file + self.floating_mean = ( + mean([v for k, v in self.price_dict.items() if k >= self.now_dt]) * 0.9 + ) diff --git a/custom_components/peaqhvac/service/hvac/water_heater/models/waterbooster_model.py b/custom_components/peaqhvac/service/hvac/water_heater/models/waterbooster_model.py index ce6952cf..5e9a7600 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/models/waterbooster_model.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/models/waterbooster_model.py @@ -4,11 +4,12 @@ from custom_components.peaqhvac.service.observer.event_property import EventProperty + class BusFireOnceMixin: _event_log = [] def bus_fire_once(self, event, data, next_start=None): - if next_start not in self._event_log: + if next_start not in self._event_log: self._hass.bus.fire(event, data) if next_start: self._event_log.append(next_start) diff --git a/custom_components/peaqhvac/service/hvac/water_heater/water_heater_coordinator.py b/custom_components/peaqhvac/service/hvac/water_heater/water_heater_coordinator.py index 324a72e6..c2d9cf61 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/water_heater_coordinator.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/water_heater_coordinator.py @@ -15,16 +15,18 @@ from custom_components.peaqhvac.service.hvac.water_heater.const import ( WAITTIMER_TIMEOUT, HIGHTEMP_THRESHOLD, - LOWTEMP_THRESHOLD + LOWTEMP_THRESHOLD, ) from custom_components.peaqhvac.service.hvac.water_heater.water_heater_next_start import ( NextWaterBoost, - NextStartPostModel + NextStartPostModel, ) from custom_components.peaqhvac.service.models.enums.demand import Demand from custom_components.peaqhvac.service.models.enums.hvac_presets import HvacPresets from homeassistant.helpers.event import async_track_time_interval -from custom_components.peaqhvac.service.hvac.water_heater.models.waterbooster_model import WaterBoosterModel +from custom_components.peaqhvac.service.hvac.water_heater.models.waterbooster_model import ( + WaterBoosterModel, +) _LOGGER = logging.getLogger(__name__) @@ -70,13 +72,15 @@ def temperature_trend(self) -> float: def latest_boost_call(self) -> str: """For Lovelace-purposes. Converts and returns epoch-timer to readable datetime-string""" if self.model.latest_boost_call > 0 and self.control_module: - return time.strftime("%Y-%m-%d %H:%M", time.localtime(self.model.latest_boost_call)) + return time.strftime( + "%Y-%m-%d %H:%M", time.localtime(self.model.latest_boost_call) + ) return "-" def import_latest_boost_call(self, strtime): new = 0 try: - if strtime != '-': + if strtime != "-": struct_time = time.strptime(strtime, "%Y-%m-%d %H:%M") new = time.mktime(struct_time) except ValueError as e: @@ -100,7 +104,8 @@ def current_temperature(self, val): self._update_demand() if self.demand.value != old_demand: _LOGGER.debug( - f"Water temp changed to {val} which caused demand to change from {old_demand} to {self.demand.value}") + f"Water temp changed to {val} which caused demand to change from {old_demand} to {self.demand.value}" + ) self.observer.broadcast(ObserverTypes.WatertempChange) self._update_operation() except ValueError as e: @@ -125,8 +130,8 @@ def _update_demand(self): self._demand = self._get_demand() def _get_demand(self): - #ret = get_demand(self.current_temperature) - #return ret + # ret = get_demand(self.current_temperature) + # return ret return Demand.NoDemand @property @@ -138,7 +143,9 @@ def water_heating(self) -> bool: def next_water_heater_start(self) -> datetime: next_start = self.model.next_water_heater_start if next_start < datetime.now() + timedelta(minutes=10): - self.model.bus_fire_once("peaqhvac.water_heater_warning", {"new": True}, next_start) + self.model.bus_fire_once( + "peaqhvac.water_heater_warning", {"new": True}, next_start + ) return next_start def _get_next_start(self) -> int | None: @@ -150,7 +157,8 @@ def _get_next_start(self) -> int | None: return None model = NextStartPostModel( - prices=self.hub.spotprice.model.prices + self.hub.spotprice.model.prices_tomorrow, + prices=self.hub.spotprice.model.prices + + self.hub.spotprice.model.prices_tomorrow, non_hours=self.hub.options.heating_options.non_hours_water_boost, demand_hours=self.hub.options.heating_options.demand_hours_water_boost, current_temp=self.current_temperature, @@ -176,7 +184,10 @@ async def async_update_operation(self, caller=None): self._update_operation() def _check_and_reset_boost(self) -> None: - if self.model.water_boost.value and self.model.latest_boost_call - time.time() > 3600: + if ( + self.model.water_boost.value + and self.model.latest_boost_call - time.time() > 3600 + ): _LOGGER.debug("Water boost has been on for more than an hour. Turning off.") self.model.water_boost.value = False @@ -193,16 +204,21 @@ def _set_water_heater_operation(self, target_temp: int) -> None: target_temp = self._get_next_start() try: if target_temp: - self.__set_toggle_boost_next_start(self.model.next_water_heater_start, target_temp) + self.__set_toggle_boost_next_start( + self.model.next_water_heater_start, target_temp + ) except Exception as e: _LOGGER.error(f"Could not check water-state: {e}") - def __set_toggle_boost_next_start(self, next_start: datetime, target: float = None) -> None: + def __set_toggle_boost_next_start( + self, next_start: datetime, target: float = None + ) -> None: try: if next_start <= datetime.now() and not self.model.water_boost.value: if target is not None and target > self.current_temperature: _LOGGER.debug( - f"Water boost is needed. Target temp is {target} and current temp is {self.current_temperature}. Next start is {next_start}") + f"Water boost is needed. Target temp is {target} and current temp is {self.current_temperature}. Next start is {next_start}" + ) self.model.water_boost.value = True self.model.latest_boost_call = time.time() self.observer.broadcast("water boost start", target) @@ -210,9 +226,14 @@ def __set_toggle_boost_next_start(self, next_start: datetime, target: float = No _LOGGER.warning(f"Could not set water boost: {e}") def __is_below_start_threshold(self) -> bool: - return all([ - datetime.now().minute >= 30, - self.hub.sensors.peaqev_facade.below_start_threshold]) + return all( + [ + datetime.now().minute >= 30, + self.hub.sensors.peaqev_facade.below_start_threshold, + ] + ) def __is_price_below_min_price(self) -> bool: - return float(self.hub.spotprice.state) <= float(self.hub.sensors.peaqev_facade.min_price) + return float(self.hub.spotprice.state) <= float( + self.hub.sensors.peaqev_facade.min_price + ) diff --git a/custom_components/peaqhvac/service/hvac/water_heater/water_heater_next_start.py b/custom_components/peaqhvac/service/hvac/water_heater/water_heater_next_start.py index f7531309..2476a17f 100644 --- a/custom_components/peaqhvac/service/hvac/water_heater/water_heater_next_start.py +++ b/custom_components/peaqhvac/service/hvac/water_heater/water_heater_next_start.py @@ -32,7 +32,7 @@ class NextStartPostModel: temp_trend: float min_price: float = 0 hvac_preset: HvacPresets = HvacPresets.Normal - latest_boost: datetime|None = None + latest_boost: datetime | None = None dt: datetime = datetime.now() def __post_init__(self): @@ -70,7 +70,13 @@ def get_next_start(self, model: NextStartPostModel) -> NextStartExportModel: return NextStartExportModel(selected.time, selected.target_temp) @staticmethod - def _calculate_target_temp_for_hour(temp_at_time: float, is_demand: bool, price: float, price_spread:float, min_price:float) -> int: + def _calculate_target_temp_for_hour( + temp_at_time: float, + is_demand: bool, + price: float, + price_spread: float, + min_price: float, + ) -> int: target = TARGET_TEMP if price > min_price else MAX_TARGET_TEMP if int(target - temp_at_time) <= 0: return target @@ -84,70 +90,106 @@ def _calculate_target_temp_for_hour(temp_at_time: float, is_demand: bool, price: if is_demand: add_temp += 10 - return min(int(temp_at_time+add_temp), target) + return min(int(temp_at_time + add_temp), target) @staticmethod - def _get_temperature_at_datetime(now_dt, target_dt, current_temp, temp_trend) -> float: + def _get_temperature_at_datetime( + now_dt, target_dt, current_temp, temp_trend + ) -> float: delay = (target_dt - now_dt).total_seconds() / 3600 return max(10, round(current_temp + (delay * temp_trend), 1)) def _add_data_list(self, model: NextStartPostModel) -> list: data = [] - for idx, p in enumerate(model.prices[self.dt.hour:], start=self.dt.hour): - new_hour = (self.dt + timedelta(hours=idx - self.dt.hour)).replace(minute=50, second=0, microsecond=0) + for idx, p in enumerate(model.prices[self.dt.hour :], start=self.dt.hour): + new_hour = (self.dt + timedelta(hours=idx - self.dt.hour)).replace( + minute=50, second=0, microsecond=0 + ) second_hour = self.dt + timedelta(hours=idx - self.dt.hour + 1) - temp_at_time = self._get_temperature_at_datetime(self.dt, new_hour, model.current_temp, model.temp_trend) + temp_at_time = self._get_temperature_at_datetime( + self.dt, new_hour, model.current_temp, model.temp_trend + ) if new_hour < self.reset_hour(self.dt): continue - data.append(PriceData( - p, - round(p / mean(model.prices[idx - self.dt.hour:]), 2), - new_hour, - temp_at_time, - self._calculate_is_cold(temp_at_time, second_hour, model, p, - model.prices[idx + 1] if idx + 1 < len(model.prices) else 9999), - second_hour.hour in model.demand_hours, - new_hour.hour in model.non_hours or second_hour.hour in model.non_hours, - self._calculate_target_temp_for_hour(temp_at_time, second_hour.hour in model.demand_hours, p, - round(p / mean(model.prices[idx - self.dt.hour:]), 2), - model.min_price) - )) + data.append( + PriceData( + p, + round(p / mean(model.prices[idx - self.dt.hour :]), 2), + new_hour, + temp_at_time, + self._calculate_is_cold( + temp_at_time, + second_hour, + model, + p, + model.prices[idx + 1] if idx + 1 < len(model.prices) else 9999, + ), + second_hour.hour in model.demand_hours, + new_hour.hour in model.non_hours + or second_hour.hour in model.non_hours, + self._calculate_target_temp_for_hour( + temp_at_time, + second_hour.hour in model.demand_hours, + p, + round(p / mean(model.prices[idx - self.dt.hour :]), 2), + model.min_price, + ), + ) + ) return data - def _calculate_is_cold(self, temp_at_time: float, second_hour: datetime, model: NextStartPostModel, p: float, p2: float) -> bool: + def _calculate_is_cold( + self, + temp_at_time: float, + second_hour: datetime, + model: NextStartPostModel, + p: float, + p2: float, + ) -> bool: calculated_water_limit = self.water_limit if p < model.min_price and p2 < self.min_price: - return temp_at_time <= calculated_water_limit+5 + return temp_at_time <= calculated_water_limit + 5 if second_hour.hour in model.demand_hours: - return temp_at_time <= calculated_water_limit+2 + return temp_at_time <= calculated_water_limit + 2 return temp_at_time <= calculated_water_limit - def reset_hour(self, dt) -> datetime: - return dt.replace(minute=0,second=0,microsecond=0) + return dt.replace(minute=0, second=0, microsecond=0) def get_data(self, model: NextStartPostModel) -> list: if model.latest_boost is not None: if self.dt - model.latest_boost < timedelta(hours=1): - self.dt = self.dt+timedelta(hours=1) + self.dt = self.dt + timedelta(hours=1) data = self._add_data_list(model) return data def get_selected(self, data: list) -> PriceData: selected: PriceData = None for d in data: - if all([ - d.is_cold, - (d.price_spread < 1 or d.price < self.min_price or (d.is_demand or d.water_temp < d.target_temp)), - not d.is_non, - d.time >= self.reset_hour(self.dt) - ]): + if all( + [ + d.is_cold, + ( + d.price_spread < 1 + or d.price < self.min_price + or (d.is_demand or d.water_temp < d.target_temp) + ), + not d.is_non, + d.time >= self.reset_hour(self.dt), + ] + ): selected = d break return selected def get_filtered(self, data: list, selected: PriceData) -> list: - return [d for d in data if max(d.time, selected.time) - min(d.time, selected.time) <= timedelta(hours=2) and d.time >= self.reset_hour(self.dt)] + return [ + d + for d in data + if max(d.time, selected.time) - min(d.time, selected.time) + <= timedelta(hours=2) + and d.time >= self.reset_hour(self.dt) + ] def get_final_selected(self, filtered: list, selected: PriceData) -> PriceData: for fdemand in [d for d in filtered if d.is_demand and not d.is_non]: @@ -157,7 +199,12 @@ def get_final_selected(self, filtered: list, selected: PriceData) -> PriceData: return selected for d in sorted(filtered, key=lambda x: (not x.is_demand, x.price_spread)): - if not d.is_non and d.price_spread < selected.price_spread and not selected.is_demand and not selected.water_temp < self.low_water_limit: + if ( + not d.is_non + and d.price_spread < selected.price_spread + and not selected.is_demand + and not selected.water_temp < self.low_water_limit + ): selected = d break return selected diff --git a/custom_components/peaqhvac/service/models/config_model.py b/custom_components/peaqhvac/service/models/config_model.py index f8d1e142..2006ddc0 100644 --- a/custom_components/peaqhvac/service/models/config_model.py +++ b/custom_components/peaqhvac/service/models/config_model.py @@ -4,10 +4,12 @@ from peaqevcore.common.models.observer_types import ObserverTypes -from custom_components.peaqhvac.const import (HVACBRAND_IVT, HVACBRAND_NIBE, - HVACBRAND_THERMIA) -from custom_components.peaqhvac.service.models.enums.hvacbrands import \ - HvacBrand +from custom_components.peaqhvac.const import ( + HVACBRAND_IVT, + HVACBRAND_NIBE, + HVACBRAND_THERMIA, +) +from custom_components.peaqhvac.service.models.enums.hvacbrands import HvacBrand _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/peaqhvac/service/models/enums/group_type.py b/custom_components/peaqhvac/service/models/enums/group_type.py index 469bc013..b77e9a46 100644 --- a/custom_components/peaqhvac/service/models/enums/group_type.py +++ b/custom_components/peaqhvac/service/models/enums/group_type.py @@ -1,5 +1,6 @@ from enum import Enum + class GroupType(Enum): LOW = "cheap" MID = "medium" diff --git a/custom_components/peaqhvac/service/models/enums/hvac_presets.py b/custom_components/peaqhvac/service/models/enums/hvac_presets.py index edd1905a..7d534e12 100644 --- a/custom_components/peaqhvac/service/models/enums/hvac_presets.py +++ b/custom_components/peaqhvac/service/models/enums/hvac_presets.py @@ -1,7 +1,6 @@ from enum import Enum -from homeassistant.components.climate.const import (PRESET_AWAY, PRESET_ECO, - PRESET_NONE) +from homeassistant.components.climate.const import PRESET_AWAY, PRESET_ECO, PRESET_NONE class HvacPresets(Enum): diff --git a/custom_components/peaqhvac/service/models/ihvac_model.py b/custom_components/peaqhvac/service/models/ihvac_model.py index 4f0e7995..467fc485 100644 --- a/custom_components/peaqhvac/service/models/ihvac_model.py +++ b/custom_components/peaqhvac/service/models/ihvac_model.py @@ -9,4 +9,3 @@ class IHvacModel: # current_offset_dict_combined: dict = field(default_factory=lambda: {}) listenerentities: list = field(default_factory=lambda: []) - diff --git a/custom_components/peaqhvac/service/models/offset_model.py b/custom_components/peaqhvac/service/models/offset_model.py index 50cd5243..61b4216b 100644 --- a/custom_components/peaqhvac/service/models/offset_model.py +++ b/custom_components/peaqhvac/service/models/offset_model.py @@ -19,9 +19,15 @@ class OffsetModel: def __init__(self, hub): self.hub = hub - async_track_time_interval(self.hub.state_machine, self.recalculate_tolerance, timedelta(seconds=120)) - self.hub.observer.add(ObserverTypes.HvacToleranceChanged, self.recalculate_tolerance) - self.hub.observer.add(ObserverTypes.TemperatureOutdoorsChanged, self.recalculate_tolerance) + async_track_time_interval( + self.hub.state_machine, self.recalculate_tolerance, timedelta(seconds=120) + ) + self.hub.observer.add( + ObserverTypes.HvacToleranceChanged, self.recalculate_tolerance + ) + self.hub.observer.add( + ObserverTypes.TemperatureOutdoorsChanged, self.recalculate_tolerance + ) @property def peaks_today(self) -> list: @@ -51,12 +57,19 @@ def tolerance(self, val): @property def current_offset_dict(self) -> dict: - return {k: v for k, v in self.calculated_offsets.items() if k.date() == datetime.now().date()} + return { + k: v + for k, v in self.calculated_offsets.items() + if k.date() == datetime.now().date() + } @property def current_offset_dict_tomorrow(self) -> dict: - return {k: v for k, v in self.calculated_offsets.items() if - k.date() == datetime.now().date() + timedelta(days=1)} + return { + k: v + for k, v in self.calculated_offsets.items() + if k.date() == datetime.now().date() + timedelta(days=1) + } def recalculate_tolerance(self, *args): if self.hub.options.hvac_tolerance is not None: @@ -72,7 +85,9 @@ def recalculate_tolerance(self, *args): ) except Exception as e: self._tolerance = self.hub.options.hvac_tolerance - _LOGGER.warning(f"Error on recalculation of tolerance. Setting default. {e}") + _LOGGER.warning( + f"Error on recalculation of tolerance. Setting default. {e}" + ) if any([old_raw != self.tolerance_raw, old_tolerance != self.tolerance]): _LOGGER.debug( f"Tolerance has been updated. New tol is {self.tolerance} and raw is {self.tolerance_raw} for temp {self.hub.sensors.average_temp_outdoors.value}" @@ -95,7 +110,9 @@ def get_tolerance_difference(self, current_temp) -> int: if tolerance_difference != self._tolerance_difference: self._tolerance_difference = tolerance_difference - _LOGGER.debug(f"Lowering tolerance with {tolerance_difference} based on current temperature {current_temp}C.") + _LOGGER.debug( + f"Lowering tolerance with {tolerance_difference} based on current temperature {current_temp}C." + ) return tolerance_difference @staticmethod diff --git a/custom_components/peaqhvac/service/models/offsets_exportmodel.py b/custom_components/peaqhvac/service/models/offsets_exportmodel.py index edc03385..2c38aa82 100644 --- a/custom_components/peaqhvac/service/models/offsets_exportmodel.py +++ b/custom_components/peaqhvac/service/models/offsets_exportmodel.py @@ -1,6 +1,7 @@ from typing import Tuple from dataclasses import dataclass, field + @dataclass class OffsetsExportModel: peaks: Tuple[list, list] @@ -13,7 +14,7 @@ def raw_offsets(self) -> list: return self._raw_offsets @raw_offsets.setter - def raw_offsets(self, val:dict) -> None: + def raw_offsets(self, val: dict) -> None: self._raw_offsets = self._offset_dict_to_list(val) @property @@ -21,7 +22,7 @@ def current_offset(self) -> list: return self._current_offset @current_offset.setter - def current_offset(self, val:dict) -> None: + def current_offset(self, val: dict) -> None: self._current_offset = self._offset_dict_to_list(val) @property @@ -29,9 +30,9 @@ def current_offset_tomorrow(self) -> list: return self._current_offset_tomorrow @current_offset_tomorrow.setter - def current_offset_tomorrow(self, val:dict) -> None: + def current_offset_tomorrow(self, val: dict) -> None: self._current_offset_tomorrow = self._offset_dict_to_list(val) @staticmethod def _offset_dict_to_list(input: dict) -> list: - return [i for i in input.values()] \ No newline at end of file + return [i for i in input.values()] diff --git a/custom_components/peaqhvac/service/models/prognosis_export_model.py b/custom_components/peaqhvac/service/models/prognosis_export_model.py index cc89b242..80d5e08c 100644 --- a/custom_components/peaqhvac/service/models/prognosis_export_model.py +++ b/custom_components/peaqhvac/service/models/prognosis_export_model.py @@ -13,4 +13,4 @@ class PrognosisExportModel: _base_temp: float = field(repr=False) def __post_init__(self): - self.delta_temp_from_now=round(self.windchill_temp - self._base_temp, 1) \ No newline at end of file + self.delta_temp_from_now = round(self.windchill_temp - self._base_temp, 1) diff --git a/custom_components/peaqhvac/service/models/weather_object.py b/custom_components/peaqhvac/service/models/weather_object.py index 6c70de25..685257ac 100644 --- a/custom_components/peaqhvac/service/models/weather_object.py +++ b/custom_components/peaqhvac/service/models/weather_object.py @@ -2,8 +2,7 @@ from datetime import datetime from time import mktime, strptime -from custom_components.peaqhvac.service.models.enums.weather_type import \ - WeatherType +from custom_components.peaqhvac.service.models.enums.weather_type import WeatherType @dataclass diff --git a/custom_components/peaqhvac/service/observer/event_property.py b/custom_components/peaqhvac/service/observer/event_property.py index c70d1b5c..b2829d69 100644 --- a/custom_components/peaqhvac/service/observer/event_property.py +++ b/custom_components/peaqhvac/service/observer/event_property.py @@ -1,5 +1,6 @@ from datetime import datetime + class EventProperty: def __init__(self, name, prop_type: type, hass, default=None): self._value = default @@ -25,10 +26,11 @@ def _is_timeout(self) -> bool: if self._timeout is None: return False return self._timeout < datetime.now() + @property def timeout(self): return self._timeout @timeout.setter - def timeout(self, val: datetime|None): - self._timeout = val \ No newline at end of file + def timeout(self, val: datetime | None): + self._timeout = val diff --git a/custom_components/peaqhvac/service/observer/iobserver_coordinator.py b/custom_components/peaqhvac/service/observer/iobserver_coordinator.py index 6826640e..2140ed09 100644 --- a/custom_components/peaqhvac/service/observer/iobserver_coordinator.py +++ b/custom_components/peaqhvac/service/observer/iobserver_coordinator.py @@ -8,17 +8,15 @@ from peaqevcore.common.models.observer_types import ObserverTypes -from custom_components.peaqhvac.service.observer.const import ( - COMMAND_WAIT, TIMEOUT) -from custom_components.peaqhvac.service.observer.models.command import \ - Command -from custom_components.peaqhvac.service.observer.models.observer_model import \ - ObserverModel +from custom_components.peaqhvac.service.observer.const import COMMAND_WAIT, TIMEOUT +from custom_components.peaqhvac.service.observer.models.command import Command +from custom_components.peaqhvac.service.observer.models.observer_model import ( + ObserverModel, +) _LOGGER = logging.getLogger(__name__) - class IObserver: """ Observer class handles updates throughout peaqev. @@ -38,28 +36,30 @@ def activate(self, init_broadcast: ObserverTypes = None) -> None: def deactivate(self) -> None: self.model.active = False - def _check_and_convert_enum_type(self, command) -> ObserverTypes|str: + def _check_and_convert_enum_type(self, command) -> ObserverTypes | str: if isinstance(command, str): try: command = ObserverTypes(command) - _LOGGER.warning(f"Observer.add: command {command} was not of type ObserverTypes but was converted.") + _LOGGER.warning( + f"Observer.add: command {command} was not of type ObserverTypes but was converted." + ) except ValueError: pass - #return ObserverTypes.Test + # return ObserverTypes.Test return command - def add(self, command: ObserverTypes|str, func): + def add(self, command: ObserverTypes | str, func): command = self._check_and_convert_enum_type(command) if command in self.model.subscribers.keys(): self.model.subscribers[command].append(func) else: self.model.subscribers[command] = [func] - async def async_broadcast(self, command: ObserverTypes|str, argument=None): + async def async_broadcast(self, command: ObserverTypes | str, argument=None): command = self._check_and_convert_enum_type(command) self.broadcast(command, argument) - def broadcast(self, command: ObserverTypes|str, argument=None): + def broadcast(self, command: ObserverTypes | str, argument=None): command = self._check_and_convert_enum_type(command) _expiration = time.time() + TIMEOUT cc = Command(command, _expiration, argument) diff --git a/custom_components/peaqhvac/service/observer/models/observer_model.py b/custom_components/peaqhvac/service/observer/models/observer_model.py index 1b7cb4c8..f9740fc8 100644 --- a/custom_components/peaqhvac/service/observer/models/observer_model.py +++ b/custom_components/peaqhvac/service/observer/models/observer_model.py @@ -1,6 +1,7 @@ from dataclasses import dataclass, field from custom_components.peaqhvac.service.observer.models.command import Command + @dataclass class ObserverModel: subscribers: dict = field(default_factory=lambda: {}) diff --git a/custom_components/peaqhvac/service/observer/observer_coordinator.py b/custom_components/peaqhvac/service/observer/observer_coordinator.py index ba1498eb..92795b90 100644 --- a/custom_components/peaqhvac/service/observer/observer_coordinator.py +++ b/custom_components/peaqhvac/service/observer/observer_coordinator.py @@ -15,14 +15,10 @@ class Observer(IObserver): def __init__(self, hass): self.hass = hass super().__init__() - async_track_time_interval( - self.hass, self.async_dispatch, timedelta(seconds=1) - ) + async_track_time_interval(self.hass, self.async_dispatch, timedelta(seconds=1)) async def async_broadcast_separator(self, func, command: Command): if await async_iscoroutine(func): await self.async_call_func(func=func, command=command), else: - await self.hass.async_add_executor_job( - self._call_func, func, command - ) + await self.hass.async_add_executor_job(self._call_func, func, command) diff --git a/custom_components/peaqhvac/services.py b/custom_components/peaqhvac/services.py index c42ffd96..8bb519e9 100644 --- a/custom_components/peaqhvac/services.py +++ b/custom_components/peaqhvac/services.py @@ -5,22 +5,18 @@ async def async_setup_services(hass, hub) -> None: async def servicehandler_enable(call): # pylint:disable=unused-argument await hub.call_enable_peaq() - async def servicehandler_disable(call): # pylint:disable=unused-argument await hub.call_disable_peaq() - async def servicehandler_set_mode(call): mode = call.data.get("mode") await hub.call_set_mode(mode) - async def servicehandler_boost_water(call): target = call.data.get("targettemp") if 10 < target < 60: hub.observer.broadcast("water boost start", target) - hass.services.async_register(DOMAIN, "enable", servicehandler_enable) hass.services.async_register(DOMAIN, "disable", servicehandler_disable) hass.services.async_register(DOMAIN, "set_mode", servicehandler_set_mode) diff --git a/custom_components/peaqhvac/switch.py b/custom_components/peaqhvac/switch.py index b1afbdae..5ba29eb8 100644 --- a/custom_components/peaqhvac/switch.py +++ b/custom_components/peaqhvac/switch.py @@ -1,9 +1,9 @@ import logging from datetime import timedelta -from homeassistant.components.switch import SwitchEntity # pylint: disable=E0401 -from homeassistant.core import HomeAssistant # pylint: disable=E0401 -from homeassistant.helpers.restore_state import RestoreEntity # pylint: disable=E0401 +from homeassistant.components.switch import SwitchEntity # pylint: disable=E0401 +from homeassistant.core import HomeAssistant # pylint: disable=E0401 +from homeassistant.helpers.restore_state import RestoreEntity # pylint: disable=E0401 from .const import DOMAIN @@ -16,6 +16,7 @@ CONTROL_HEAT = "control heat" CONTROL_VENTILATION = "control ventilation" + async def async_setup_entry( hass: HomeAssistant, config_entry, async_add_entities ): # pylint:disable=unused-argument diff --git a/custom_components/peaqhvac/test/test_offsets.py b/custom_components/peaqhvac/test/test_offsets.py index 81889061..eaa3d594 100644 --- a/custom_components/peaqhvac/test/test_offsets.py +++ b/custom_components/peaqhvac/test/test_offsets.py @@ -2,28 +2,206 @@ import random import pytest from ..service.hvac.house_heater.models.calculated_offset import CalculatedOffsetModel -from ..service.hvac.offset.offset_utils import (offset_per_day, set_offset_dict, adjust_to_threshold) +from ..service.hvac.offset.offset_utils import ( + offset_per_day, + set_offset_dict, + adjust_to_threshold, +) from ..service.hvac.offset.peakfinder import smooth_transitions from ..service.models.enums.hvac_presets import HvacPresets -P231213 = [1.17, 1.14, 1.14, 1.11, 1.11, 1.14, 1.25, 1.59, 2.09, 2.09, 2.13, 2.14, - 2.14, 1.61, 1.59, 1.62, 1.61, 1.68, 1.61, 1.52, 1.44, 1.36, 1.38, 1.27] -P231214 = [1.17, 1.15, 1.16, 1.16, 1.19, 1.24, 1.47, 1.81, 1.97, 2.19, 2.19, 1.92, - 1.81, 1.99, 2.19, 2.73, 2.73, 2.63, 2.11, 1.81, 1.62, 1.43, 1.41, 1.28] -P231215 =[1.28,1.24,1.2,1.15,1.13,1.2,1.42,1.57,1.78,1.72,1.61,1.51,1.39,1.31,1.28,1.3,1.42,1.37,1.26,1.19,1.15,1.14,0.93,1.05] -P231216 = [0.69,0.62,0.56,0.45,0.38,0.32,0.31,0.31,0.31,0.3,0.3,0.27,0.26,0.25,0.26,0.27,0.28,0.27,0.24,0.23,0.15,0.11,0.08,0.08] -P231217 = [0.06,0.06,0.06,0.06,0.07,0.08,0.08,0.08,0.1,0.11,0.11,0.13,0.11,0.13,0.11,0.14,0.16,0.24,0.27,0.27,0.25,0.24,0.17,0.16] -P231218 = [0.22, 0.2, 0.17, 0.15, 0.16, 0.22, 0.3, 0.38, 0.43, 0.4, 0.38, 0.36, 0.32, 0.32, 0.32, 0.33, 0.36, 0.4, 0.39, 0.35, 0.32, 0.29, 0.26, 0.22] -P231219 = [0.19, 0.15, 0.11, 0.1, 0.14, 0.2, 0.28, 0.41, 0.51, 0.52, 0.54, 0.51, 0.45, 0.41, 0.41, 0.4, 0.37, 0.36, 0.37, 0.32, 0.3, 0.27, 0.25, 0.24] +P231213 = [ + 1.17, + 1.14, + 1.14, + 1.11, + 1.11, + 1.14, + 1.25, + 1.59, + 2.09, + 2.09, + 2.13, + 2.14, + 2.14, + 1.61, + 1.59, + 1.62, + 1.61, + 1.68, + 1.61, + 1.52, + 1.44, + 1.36, + 1.38, + 1.27, +] +P231214 = [ + 1.17, + 1.15, + 1.16, + 1.16, + 1.19, + 1.24, + 1.47, + 1.81, + 1.97, + 2.19, + 2.19, + 1.92, + 1.81, + 1.99, + 2.19, + 2.73, + 2.73, + 2.63, + 2.11, + 1.81, + 1.62, + 1.43, + 1.41, + 1.28, +] +P231215 = [ + 1.28, + 1.24, + 1.2, + 1.15, + 1.13, + 1.2, + 1.42, + 1.57, + 1.78, + 1.72, + 1.61, + 1.51, + 1.39, + 1.31, + 1.28, + 1.3, + 1.42, + 1.37, + 1.26, + 1.19, + 1.15, + 1.14, + 0.93, + 1.05, +] +P231216 = [ + 0.69, + 0.62, + 0.56, + 0.45, + 0.38, + 0.32, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.27, + 0.26, + 0.25, + 0.26, + 0.27, + 0.28, + 0.27, + 0.24, + 0.23, + 0.15, + 0.11, + 0.08, + 0.08, +] +P231217 = [ + 0.06, + 0.06, + 0.06, + 0.06, + 0.07, + 0.08, + 0.08, + 0.08, + 0.1, + 0.11, + 0.11, + 0.13, + 0.11, + 0.13, + 0.11, + 0.14, + 0.16, + 0.24, + 0.27, + 0.27, + 0.25, + 0.24, + 0.17, + 0.16, +] +P231218 = [ + 0.22, + 0.2, + 0.17, + 0.15, + 0.16, + 0.22, + 0.3, + 0.38, + 0.43, + 0.4, + 0.38, + 0.36, + 0.32, + 0.32, + 0.32, + 0.33, + 0.36, + 0.4, + 0.39, + 0.35, + 0.32, + 0.29, + 0.26, + 0.22, +] +P231219 = [ + 0.19, + 0.15, + 0.11, + 0.1, + 0.14, + 0.2, + 0.28, + 0.41, + 0.51, + 0.52, + 0.54, + 0.51, + 0.45, + 0.41, + 0.41, + 0.4, + 0.37, + 0.36, + 0.37, + 0.32, + 0.3, + 0.27, + 0.25, + 0.24, +] def test_offsets_cent_and_normal_match(): prices = P231213 + P231214 - now_dt = datetime(2023,12,13,20,43,0) + now_dt = datetime(2023, 12, 13, 20, 43, 0) r1 = set_offset_dict(prices, now_dt, 0, {}) - r2 = set_offset_dict([p*100 for p in prices], now_dt, 0, {}) + r2 = set_offset_dict([p * 100 for p in prices], now_dt, 0, {}) assert r1 == r2 + def test_assert_cheaper_hours_tomorrow_not_lower_offset_than_today(): """error with 231217 last hours having another offset than the same price for 231216""" _tolerance = 3 @@ -40,7 +218,7 @@ def test_assert_cheaper_hours_tomorrow_not_lower_offset_than_today(): ) ret = smooth_transitions( - vals = offs2, + vals=offs2, tolerance=_tolerance, ) @@ -48,7 +226,7 @@ def test_assert_cheaper_hours_tomorrow_not_lower_offset_than_today(): print(offs2) print(ret) - #assert 1 > 2 + # assert 1 > 2 # def test_offsets_correct_curve_over_night_cached_today(): @@ -94,13 +272,14 @@ def test_assert_cheaper_hours_tomorrow_not_lower_offset_than_today(): # # assert [v for k, v in ret2.items()] == key_today + def test_smooth_transistions_no_weather_prog_nothing_exceeds_tolerance(): _tolerance = 3 indoors_preset = HvacPresets.Normal prices = P231213 prices_tomorrow = P231214 now_dt = datetime(2023, 12, 13, 20, 43, 0) - offset_dict = set_offset_dict(prices+prices_tomorrow, now_dt, 0, {}) + offset_dict = set_offset_dict(prices + prices_tomorrow, now_dt, 0, {}) print(offset_dict) offsets = offset_per_day( @@ -111,12 +290,13 @@ def test_smooth_transistions_no_weather_prog_nothing_exceeds_tolerance(): ) assert all([abs(v) <= _tolerance for k, v in offsets.items()]) ret = smooth_transitions( - vals=offsets, - tolerance=_tolerance, - ) + vals=offsets, + tolerance=_tolerance, + ) assert all([abs(v) <= _tolerance for k, v in ret.items()]) + def test_adjust_to_treshold_no_exceeding_values(): _tolerance = 3 indoors_preset = HvacPresets.Normal @@ -137,8 +317,12 @@ def test_adjust_to_treshold_no_exceeding_values(): tolerance=_tolerance, ) - for k,v in smooth.items(): - model = CalculatedOffsetModel(current_offset=v, current_tempdiff=random.uniform(-1, 1), current_temp_extremas=random.uniform(-1, 1), current_temp_trend_offset=random.uniform(-1, 1)) + for k, v in smooth.items(): + model = CalculatedOffsetModel( + current_offset=v, + current_tempdiff=random.uniform(-1, 1), + current_temp_extremas=random.uniform(-1, 1), + current_temp_trend_offset=random.uniform(-1, 1), + ) adj = adjust_to_threshold(model, 0, _tolerance) assert abs(adj) <= _tolerance - diff --git a/custom_components/peaqhvac/test/test_peakfinder.py b/custom_components/peaqhvac/test/test_peakfinder.py index 943d9f6a..1d7d4af2 100644 --- a/custom_components/peaqhvac/test/test_peakfinder.py +++ b/custom_components/peaqhvac/test/test_peakfinder.py @@ -1,17 +1,65 @@ import pytest from ..service.hvac.offset.peakfinder import identify_peaks -P240910 = [0.07,0.07,0.06,0.06,0.07,0.07,0.08,0.11,0.11,0.11,0.11,0.1,0.08,0.08,0.08,0.08,0.08,0.12,0.12,0.12,0.11,0.1,0.08,0.08] -P240911 = [0.08,0.08,0.08,0.08,0.09,0.11,0.13,0.21,0.6,0.6,0.6,0.59,0.4,0.37,0.32,0.15,0.22,0.35,0.3,0.21,0.14,0.12,0.12,0.11] +P240910 = [ + 0.07, + 0.07, + 0.06, + 0.06, + 0.07, + 0.07, + 0.08, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.12, + 0.12, + 0.12, + 0.11, + 0.1, + 0.08, + 0.08, +] +P240911 = [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.09, + 0.11, + 0.13, + 0.21, + 0.6, + 0.6, + 0.6, + 0.59, + 0.4, + 0.37, + 0.32, + 0.15, + 0.22, + 0.35, + 0.3, + 0.21, + 0.14, + 0.12, + 0.12, + 0.11, +] + def test_no_peaks_found(): peaks = identify_peaks(P240910) assert peaks == [] + def test_single_peak(): peaks = identify_peaks(P240911) assert peaks == [17] - - - - diff --git a/custom_components/peaqhvac/test/test_temperature_helpers.py b/custom_components/peaqhvac/test/test_temperature_helpers.py index e40395e7..4817e2ea 100644 --- a/custom_components/peaqhvac/test/test_temperature_helpers.py +++ b/custom_components/peaqhvac/test/test_temperature_helpers.py @@ -1,22 +1,32 @@ import pytest from ..service.hub.target_temp import adjusted_tolerances -from ..service.hvac.house_heater.temperature_helper import get_temp_extremas, get_tempdiff_inverted +from ..service.hvac.house_heater.temperature_helper import ( + get_temp_extremas, + get_tempdiff_inverted, +) MINTOLERANCE = 0.2 MAXTOLERANCE = 0.5 -def _current_tolerances(determinator: bool, current_offset: int, adjust_tolerances: bool = True) -> float: + +def _current_tolerances( + determinator: bool, current_offset: int, adjust_tolerances: bool = True +) -> float: if adjust_tolerances: - tolerances= adjusted_tolerances(current_offset, MINTOLERANCE, MAXTOLERANCE) + tolerances = adjusted_tolerances(current_offset, MINTOLERANCE, MAXTOLERANCE) else: tolerances = MINTOLERANCE, MAXTOLERANCE - return tolerances[0] if (determinator > 0 or determinator is True) else tolerances[1] + return ( + tolerances[0] if (determinator > 0 or determinator is True) else tolerances[1] + ) + def test_temp_extremas_one_positive(): ter = get_temp_extremas(0, [0, 0, 0, 0, 1, 0, 0, 0, 0], _current_tolerances) assert ter == 0.8 + def test_temp_extremas_one_negative(): ter = get_temp_extremas(0, [0, 0, 0, 0, -1, 0, 0, 0, 0], _current_tolerances) assert ter == -0.5 @@ -33,6 +43,7 @@ def test_tempdiff_cold(): ret4 = get_tempdiff_inverted(-3, tempdiff, _current_tolerances) assert ret4 == 2 + def test_tempdiff_hot(): tempdiff = 0.5 ret1 = get_tempdiff_inverted(0, tempdiff, _current_tolerances) @@ -42,4 +53,4 @@ def test_tempdiff_hot(): ret3 = get_tempdiff_inverted(-2, tempdiff, _current_tolerances) assert ret3 == -1 ret4 = get_tempdiff_inverted(-3, tempdiff, _current_tolerances) - assert ret4 == -1 \ No newline at end of file + assert ret4 == -1 diff --git a/custom_components/peaqhvac/test/test_water_heater_next_start.py b/custom_components/peaqhvac/test/test_water_heater_next_start.py index fbb4ce7a..e39af115 100644 --- a/custom_components/peaqhvac/test/test_water_heater_next_start.py +++ b/custom_components/peaqhvac/test/test_water_heater_next_start.py @@ -3,13 +3,188 @@ from ..service.hvac.water_heater.water_heater_next_start import NextWaterBoost from ..service.models.enums.hvac_presets import HvacPresets -P230830 = [0.76,0.57,0.59,0.64,0.97,1.5,1.97,2.22,2.16,1.93,1.72,1.55,1.53,1.5,1.48,1.52,1.5,1.79,2.16,2.59,2.58,2.08,1.81,1.43] -P230831 = [1.42,0.8,0.61,0.59,0.6,1.4,1.64,2.17,1.97,1.77,1.51,1.44,1.41,1.38,1.36,1.37,1.4,1.55,1.84,2.28,2.14,1.94,1.62,1.47] -P230901 = [0.38,0.37,0.36,0.36,0.36,0.37,0.4,1.17,1.95,1.69,1.56,1.47,1.41,0.5,0.47,0.52,0.53,0.55,0.55,0.52,0.47,0.43,0.4,0.38] -P230902=[0.36,0.35,0.34,0.33,0.33,0.32,0.35,0.37,0.37,0.38,0.38,0.38,0.37,0.37,0.37,0.38,0.4,0.43,0.44,0.45,0.45,0.43,0.4,0.39] -P230903=[0.37,0.37,0.36,0.36,0.37,0.37,0.37,0.39,0.41,0.42,0.44,0.44,0.39,0.25,0.15,0.31,0.45,0.45,0.44,0.42,0.37,0.32,0.22,0.1] -P231214 =[1.17,1.15,1.16,1.16,1.19,1.24,1.47,1.81,1.97,2.19,2.19,1.92,1.81,1.99,2.19,2.73,2.73,2.63,2.11,1.81,1.62,1.43,1.41,1.28] -P231215 =[1.28,1.24,1.2,1.15,1.13,1.2,1.42,1.57,1.78,1.72,1.61,1.51,1.39,1.31,1.28,1.3,1.42,1.37,1.26,1.19,1.15,1.14,0.93,1.05] +P230830 = [ + 0.76, + 0.57, + 0.59, + 0.64, + 0.97, + 1.5, + 1.97, + 2.22, + 2.16, + 1.93, + 1.72, + 1.55, + 1.53, + 1.5, + 1.48, + 1.52, + 1.5, + 1.79, + 2.16, + 2.59, + 2.58, + 2.08, + 1.81, + 1.43, +] +P230831 = [ + 1.42, + 0.8, + 0.61, + 0.59, + 0.6, + 1.4, + 1.64, + 2.17, + 1.97, + 1.77, + 1.51, + 1.44, + 1.41, + 1.38, + 1.36, + 1.37, + 1.4, + 1.55, + 1.84, + 2.28, + 2.14, + 1.94, + 1.62, + 1.47, +] +P230901 = [ + 0.38, + 0.37, + 0.36, + 0.36, + 0.36, + 0.37, + 0.4, + 1.17, + 1.95, + 1.69, + 1.56, + 1.47, + 1.41, + 0.5, + 0.47, + 0.52, + 0.53, + 0.55, + 0.55, + 0.52, + 0.47, + 0.43, + 0.4, + 0.38, +] +P230902 = [ + 0.36, + 0.35, + 0.34, + 0.33, + 0.33, + 0.32, + 0.35, + 0.37, + 0.37, + 0.38, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.38, + 0.4, + 0.43, + 0.44, + 0.45, + 0.45, + 0.43, + 0.4, + 0.39, +] +P230903 = [ + 0.37, + 0.37, + 0.36, + 0.36, + 0.37, + 0.37, + 0.37, + 0.39, + 0.41, + 0.42, + 0.44, + 0.44, + 0.39, + 0.25, + 0.15, + 0.31, + 0.45, + 0.45, + 0.44, + 0.42, + 0.37, + 0.32, + 0.22, + 0.1, +] +P231214 = [ + 1.17, + 1.15, + 1.16, + 1.16, + 1.19, + 1.24, + 1.47, + 1.81, + 1.97, + 2.19, + 2.19, + 1.92, + 1.81, + 1.99, + 2.19, + 2.73, + 2.73, + 2.63, + 2.11, + 1.81, + 1.62, + 1.43, + 1.41, + 1.28, +] +P231215 = [ + 1.28, + 1.24, + 1.2, + 1.15, + 1.13, + 1.2, + 1.42, + 1.57, + 1.78, + 1.72, + 1.61, + 1.51, + 1.39, + 1.31, + 1.28, + 1.3, + 1.42, + 1.37, + 1.26, + 1.19, + 1.15, + 1.14, + 0.93, + 1.05, +] MIN_DEMAND = 26 # def test_start_time_water_is_cold_no_non_hours(): diff --git a/custom_components/peaqhvac/test/test_water_heater_next_start_new.py b/custom_components/peaqhvac/test/test_water_heater_next_start_new.py index 175c7f10..22c0b575 100644 --- a/custom_components/peaqhvac/test/test_water_heater_next_start_new.py +++ b/custom_components/peaqhvac/test/test_water_heater_next_start_new.py @@ -1,30 +1,260 @@ import pytest from datetime import datetime -from ..service.hvac.water_heater.water_heater_next_start import NextWaterBoost, NextStartPostModel +from ..service.hvac.water_heater.water_heater_next_start import ( + NextWaterBoost, + NextStartPostModel, +) -P240126 = [0.97,0.94,0.91,0.87,0.86,0.82,0.9,0.97,1,0.98,0.95,0.91,0.82,0.74,0.78,0.77,0.81,0.89,0.85,0.55,0.47,0.44,0.42,0.39] -P240129 = [0.07,0.05,0.05,0.05,0.06,0.08,0.16,0.35,0.37,0.43,0.65,0.76,0.97,0.97,0.98,1.34,1.57,1.83,1.69,1.55,1.34,1.24,0.99,0.96] -P240130 = [0.93,0.79,0.76,0.76,0.76,0.93,0.96,1.08,1.26,1.24,1.14,1,0.98,0.98,0.99,0.97,0.98,1.07,0.95,0.91,0.88,0.84,0.4,0.42] -P240131 = [0.34,0.34,0.34,0.34,0.35,0.35,0.44,0.87,0.9,0.53,0.37,0.35,0.34,0.33,0.32,0.31,0.32,0.31,0.22,0.13,0.08,0.08,0.07,0.05] -P240201 = [0.05,0.05,0.04,0.04,0.05,0.05,0.08,0.12,0.13,0.14,0.13,0.12,0.13,0.12,0.13,0.15,0.25,0.57,0.65,0.28,0.31,0.3,0.25,0.18] -P240202 = [0.19,0.21,0.21,0.23,0.24,0.26,0.37,0.9,0.93,0.9,0.88,0.69,0.43,0.41,0.4,0.4,0.38,0.37,0.34,0.28,0.24,0.18,0.09,0.08] -P240203 = [0.06,0.06,0.05,0.05,0.05,0.05,0.07,0.08,0.08,0.11,0.11,0.08,0.08,0.08,0.08,0.09,0.13,0.22,0.22,0.13,0.08,0.1,0.08,0.08] -P240314 = [0.45,0.37,0.34,0.26,0.28,0.36,0.45,0.5,0.5,0.5,0.5,0.51,0.5,0.48,0.46,0.43,0.39,0.37,0.4,0.35,0.28,0.13,0.08,0.08] -P240315 = [0.08,0.08,0.08,0.08,0.08,0.27,0.46,0.57,0.63,0.67,0.71,0.63,0.62,0.61,0.69,0.78,0.8,0.86,0.87,0.81,0.76,0.69,0.64,0.61] +P240126 = [ + 0.97, + 0.94, + 0.91, + 0.87, + 0.86, + 0.82, + 0.9, + 0.97, + 1, + 0.98, + 0.95, + 0.91, + 0.82, + 0.74, + 0.78, + 0.77, + 0.81, + 0.89, + 0.85, + 0.55, + 0.47, + 0.44, + 0.42, + 0.39, +] +P240129 = [ + 0.07, + 0.05, + 0.05, + 0.05, + 0.06, + 0.08, + 0.16, + 0.35, + 0.37, + 0.43, + 0.65, + 0.76, + 0.97, + 0.97, + 0.98, + 1.34, + 1.57, + 1.83, + 1.69, + 1.55, + 1.34, + 1.24, + 0.99, + 0.96, +] +P240130 = [ + 0.93, + 0.79, + 0.76, + 0.76, + 0.76, + 0.93, + 0.96, + 1.08, + 1.26, + 1.24, + 1.14, + 1, + 0.98, + 0.98, + 0.99, + 0.97, + 0.98, + 1.07, + 0.95, + 0.91, + 0.88, + 0.84, + 0.4, + 0.42, +] +P240131 = [ + 0.34, + 0.34, + 0.34, + 0.34, + 0.35, + 0.35, + 0.44, + 0.87, + 0.9, + 0.53, + 0.37, + 0.35, + 0.34, + 0.33, + 0.32, + 0.31, + 0.32, + 0.31, + 0.22, + 0.13, + 0.08, + 0.08, + 0.07, + 0.05, +] +P240201 = [ + 0.05, + 0.05, + 0.04, + 0.04, + 0.05, + 0.05, + 0.08, + 0.12, + 0.13, + 0.14, + 0.13, + 0.12, + 0.13, + 0.12, + 0.13, + 0.15, + 0.25, + 0.57, + 0.65, + 0.28, + 0.31, + 0.3, + 0.25, + 0.18, +] +P240202 = [ + 0.19, + 0.21, + 0.21, + 0.23, + 0.24, + 0.26, + 0.37, + 0.9, + 0.93, + 0.9, + 0.88, + 0.69, + 0.43, + 0.41, + 0.4, + 0.4, + 0.38, + 0.37, + 0.34, + 0.28, + 0.24, + 0.18, + 0.09, + 0.08, +] +P240203 = [ + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.07, + 0.08, + 0.08, + 0.11, + 0.11, + 0.08, + 0.08, + 0.08, + 0.08, + 0.09, + 0.13, + 0.22, + 0.22, + 0.13, + 0.08, + 0.1, + 0.08, + 0.08, +] +P240314 = [ + 0.45, + 0.37, + 0.34, + 0.26, + 0.28, + 0.36, + 0.45, + 0.5, + 0.5, + 0.5, + 0.5, + 0.51, + 0.5, + 0.48, + 0.46, + 0.43, + 0.39, + 0.37, + 0.4, + 0.35, + 0.28, + 0.13, + 0.08, + 0.08, +] +P240315 = [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.27, + 0.46, + 0.57, + 0.63, + 0.67, + 0.71, + 0.63, + 0.62, + 0.61, + 0.69, + 0.78, + 0.8, + 0.86, + 0.87, + 0.81, + 0.76, + 0.69, + 0.64, + 0.61, +] + def test1(): tt = NextWaterBoost() model = NextStartPostModel( prices=P240126, - demand_hours=[20,21], - non_hours=[12,17,11,16], + demand_hours=[20, 21], + non_hours=[12, 17, 11, 16], current_temp=41.2, temp_trend=0, - latest_boost=datetime(2024,1,25, 6,52), - dt=datetime(2024,1,26,13,2,0)) + latest_boost=datetime(2024, 1, 25, 6, 52), + dt=datetime(2024, 1, 26, 13, 2, 0), + ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,1,26,20,50,0) + assert ret.next_start == datetime(2024, 1, 26, 20, 50, 0) assert ret.target_temp == 47 @@ -33,76 +263,81 @@ def test2(): model = NextStartPostModel( prices=P240129, demand_hours=[], - non_hours=[12,17,11,16], + non_hours=[12, 17, 11, 16], current_temp=37, temp_trend=0, - latest_boost=datetime(2024,1,29, 0,2), - dt=datetime(2024,1,29,12,30,0) + latest_boost=datetime(2024, 1, 29, 0, 2), + dt=datetime(2024, 1, 29, 12, 30, 0), ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,1,29,23,50,0) + assert ret.next_start == datetime(2024, 1, 29, 23, 50, 0) assert ret.target_temp == 46 + def test3(): tt = NextWaterBoost() model = NextStartPostModel( - prices=P240130+P240131, - demand_hours=[20,21], - non_hours=[12,17,11,16], + prices=P240130 + P240131, + demand_hours=[20, 21], + non_hours=[12, 17, 11, 16], current_temp=12, temp_trend=-40, - latest_boost=datetime(2024,1,30, 20,40), - dt=datetime(2024,1,30,20,55,0) + latest_boost=datetime(2024, 1, 30, 20, 40), + dt=datetime(2024, 1, 30, 20, 55, 0), ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,1,30,22,50,0) + assert ret.next_start == datetime(2024, 1, 30, 22, 50, 0) assert ret.target_temp == 25 + def test4(): tt = NextWaterBoost() model = NextStartPostModel( prices=P240201 + P240202, - demand_hours=[20,21], - non_hours=[12,17,11,16], + demand_hours=[20, 21], + non_hours=[12, 17, 11, 16], current_temp=52.6, temp_trend=-1.78, - latest_boost=datetime(2024,2,1, 14,42), - dt=datetime(2024,2,1,17,30,0) + latest_boost=datetime(2024, 2, 1, 14, 42), + dt=datetime(2024, 2, 1, 17, 30, 0), ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,2,1,23,50,0) + assert ret.next_start == datetime(2024, 2, 1, 23, 50, 0) assert ret.target_temp == 47 + def test5(): tt = NextWaterBoost() model = NextStartPostModel( prices=P240202 + P240203, - demand_hours=[20,21], - non_hours=[12,17,11,16], + demand_hours=[20, 21], + non_hours=[12, 17, 11, 16], current_temp=44.1, temp_trend=0, - latest_boost=datetime(2024,2,2, 14,42), - dt=datetime(2024,2,2,20,25,0) + latest_boost=datetime(2024, 2, 2, 14, 42), + dt=datetime(2024, 2, 2, 20, 25, 0), ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,2,3,2,50,0) + assert ret.next_start == datetime(2024, 2, 3, 2, 50, 0) assert ret.target_temp == 47 + def test6(): tt = NextWaterBoost() model = NextStartPostModel( prices=P240202 + P240203, - demand_hours=[20,21], - non_hours=[12,17,11,16], + demand_hours=[20, 21], + non_hours=[12, 17, 11, 16], current_temp=38.1, temp_trend=0, - latest_boost=datetime(2024,2,2, 14,42), - dt=datetime(2024,2,3,2,50,1) + latest_boost=datetime(2024, 2, 2, 14, 42), + dt=datetime(2024, 2, 3, 2, 50, 1), ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,2,3,2,50,0) + assert ret.next_start == datetime(2024, 2, 3, 2, 50, 0) assert ret.target_temp == 47 + def test_below_min_hours_should_heat_more(): tt = NextWaterBoost() model = NextStartPostModel( @@ -112,9 +347,9 @@ def test_below_min_hours_should_heat_more(): current_temp=48.5, temp_trend=0, min_price=0.1, - latest_boost=datetime(2024,3,13, 23,50), - dt=datetime(2024,3,14,20,9,1) + latest_boost=datetime(2024, 3, 13, 23, 50), + dt=datetime(2024, 3, 14, 20, 9, 1), ) ret = tt.get_next_start(model) - assert ret.next_start == datetime(2024,3,15,3,50,0) + assert ret.next_start == datetime(2024, 3, 15, 3, 50, 0) assert ret.target_temp == 53