Skip to content

Commit

Permalink
misc
Browse files Browse the repository at this point in the history
  • Loading branch information
magnuselden authored and magnuselden committed Oct 14, 2023
1 parent 92a6201 commit 1b06cc9
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 70 deletions.
18 changes: 9 additions & 9 deletions custom_components/peaqhvac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config.entry_id] = config.data

"""
needed in config:
check for nordpool
check for peaqev, otherwise ignore peak-management
weather? demand one integration or pick whichever?
"""

huboptions = ConfigModel()

huboptions.indoor_tempsensors = huboptions.set_sensors_from_string(
Expand All @@ -38,7 +30,15 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
huboptions.hvacbrand = huboptions.set_hvacbrand(
HVACBRAND_NIBE
) # todo:move to proper dropdown in configflow
# configinputs["hvacbrand"] = config.data["hvacbrand"]

#todo: make conf out of these
huboptions.heating_options.outdoor_temp_stop_heating = 15
huboptions.heating_options.non_hours_water_boost = [7, 11, 12, 15, 16, 17,23]
huboptions.heating_options.low_degree_minutes = -600
huboptions.heating_options.summer_temp = 17
huboptions.heating_options.very_cold_temp = -12
huboptions.heating_options.night_hours = [23,0,1,2,3,4,5]
# todo: make conf out of these

hub = Hub(hass, huboptions)

Expand Down
8 changes: 8 additions & 0 deletions custom_components/peaqhvac/service/hub/hub.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from datetime import datetime

from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.event import async_track_state_change
Expand Down Expand Up @@ -51,6 +52,13 @@ async def async_setup_trackers(self):
self.hass, self.trackerentities, self.async_state_changed
)

def price_below_min(self, hour:datetime) -> bool:
try:
return self.nordpool.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.nordpool.prices)}")
return False

@property
def is_initialized(self) -> bool:
if self._is_initialized:
Expand Down
5 changes: 1 addition & 4 deletions custom_components/peaqhvac/service/hvac/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
LOW_DEGREE_MINUTES = -600
VERY_COLD_TEMP = -12
SUMMER_TEMP = 17
NIGHT_HOURS = [23,0,1,2,3,4,5]

HEATBOOST_TIMER = 7200
WAITTIMER_TIMEOUT = 240
DEFAULT_WATER_BOOST = 1200
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from datetime import datetime, timedelta
from typing import Tuple

from custom_components.peaqhvac.service.hvac.const import LOW_DEGREE_MINUTES, WAITTIMER_TIMEOUT, HEATBOOST_TIMER
from custom_components.peaqhvac.service.hvac.const import WAITTIMER_TIMEOUT, HEATBOOST_TIMER
from custom_components.peaqhvac.service.hvac.house_heater.temperature_helper import HouseHeaterTemperatureHelper
from custom_components.peaqhvac.service.hvac.interfaces.iheater import IHeater
from custom_components.peaqhvac.service.models.enums.demand import Demand
Expand All @@ -14,7 +14,6 @@
_LOGGER = logging.getLogger(__name__)

OFFSET_MIN_VALUE = -10
OUTDOOR_TEMP_STOP_HEATING = 13

class HouseHeaterCoordinator(IHeater):
def __init__(self, hvac):
Expand Down Expand Up @@ -63,16 +62,14 @@ def _temporarily_lower_offset(self, input_offset) -> int:
_LOGGER.debug("Lowering offset -2.")
input_offset -= 2
elif self._hvac.hub.sensors.peaqev_installed:
if self._hvac.hvac_dm <= LOW_DEGREE_MINUTES:
if self._hvac.hvac_dm <= self._hvac.hub.options.heating_options.low_degree_minutes:
_LOGGER.debug("Lowering offset -1.")
input_offset -= 1
return input_offset



def get_current_offset(self, offsets: dict) -> Tuple[int, bool]:
if any([
self._hvac.hub.sensors.average_temp_outdoors.value > OUTDOOR_TEMP_STOP_HEATING,
self._hvac.hub.sensors.average_temp_outdoors.value > self._hvac.hub.options.heating_options.outdoor_temp_stop_heating,
self._hvac.hub.offset.max_price_lower(self._hvac.hub.sensors.get_tempdiff())]):
return OFFSET_MIN_VALUE, True

Expand Down Expand Up @@ -168,7 +165,10 @@ def _check_next_hour_offset(self, offsets: dict, force_update: bool) -> None:
if datetime.now().minute >= 40:
hour += timedelta(hours=1)
try:
_offset = offsets[hour]
if self._hvac.hub.price_below_min(hour):
_offset = max(offsets[hour],0)
else:
_offset = offsets[hour]
except:
_LOGGER.warning(
"No Price-offsets have been calculated. Setting base-offset to 0."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,40 @@ def get_tempdiff_inverted(self, current_offset) -> int:
_tolerance = self._determine_tolerance(diff, current_offset)
return int(diff / _tolerance) * -1

# def get_temp_extremas(self, current_offset) -> float:
# set_temp = self._hub.sensors.set_temp_indoors.adjusted_temp
# diffs = [set_temp - t for t in self._hub.sensors.average_temp_indoors.all_values]
# cold_diffs = [diff for diff in diffs if diff > 0]
# hot_diffs = [diff for diff in diffs if diff < 0]
# if len(cold_diffs) == len(hot_diffs):
# return 0
# is_cold = len(cold_diffs) > len(hot_diffs)
# tolerance = self._determine_tolerance(is_cold, current_offset)
# if is_cold:
# ret = mean(cold_diffs) - tolerance
# else:
# ret = mean(hot_diffs) + tolerance
# return round(ret, 2)

def get_temp_extremas(self, current_offset) -> float:
set_temp = self._hub.sensors.set_temp_indoors.adjusted_temp
diffs = [set_temp - t for t in self._hub.sensors.average_temp_indoors.all_values]
cold_diffs = [diff for diff in diffs if diff > 0]
hot_diffs = [diff for diff in diffs if diff < 0]
if len(cold_diffs) == len(hot_diffs):
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:
return 0
is_cold = len(cold_diffs) > len(hot_diffs)
is_cold = cold_large > hot_large
tolerance = self._determine_tolerance(is_cold, current_offset)
if is_cold:
ret = mean(cold_diffs) - tolerance
else:
ret = mean(hot_diffs) + tolerance
return round(ret, 2)
return self.temp_extremas_return(cold_diffs, tolerance)
return self.temp_extremas_return(hot_diffs, tolerance)

# def get_temp_trend_offset(self, current_offset) -> float:
# if self._hub.sensors.temp_trend_indoors.is_clean:
# if -0.1 < self._hub.sensors.temp_trend_indoors.gradient < 0.1:
# return 0
# new_temp_diff = (
# self._hub.predicted_temp
# - self._hub.sensors.set_temp_indoors.adjusted_temp
# )
# _tolerance = self._determine_tolerance(new_temp_diff, current_offset)
# if abs(new_temp_diff) >= _tolerance:
# ret = self._get_offset_steps(_tolerance)
# if new_temp_diff > 0:
# ret = ret * -1
# if ret == 0:
# return 0
# return ret
# return 0
@staticmethod
def temp_extremas_return(diffs, tolerance) -> float:
div = len(diffs) - 1
ret = (mean(diffs) - tolerance) * div
return round(ret / div, 2)

def get_temp_trend_offset(self, current_offset) -> float:
if not self._hub.sensors.temp_trend_indoors.is_clean:
Expand Down
17 changes: 8 additions & 9 deletions custom_components/peaqhvac/service/hvac/house_ventilation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from datetime import datetime, timedelta
import logging
from custom_components.peaqhvac.service.hvac.const import LOW_DEGREE_MINUTES, SUMMER_TEMP, NIGHT_HOURS, VERY_COLD_TEMP, \
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
Expand Down Expand Up @@ -34,9 +33,9 @@ async def async_check_vent_boost(self, caller=None) -> None:
self._current_vent_state = False
# else:
# self._current_vent_state = False
if self._hvac.hvac_dm < LOW_DEGREE_MINUTES + 100 or self._hvac.hub.sensors.average_temp_outdoors.value < VERY_COLD_TEMP:
if self._hvac.hvac_dm < self._hvac.hub.options.heating_options.low_degree_minutes + 100 or self._hvac.hub.sensors.average_temp_outdoors.value < self._hvac.hub.options.heating_options.very_cold_temp:
# If HVAC degree minutes are high or outdoor temperature is very cold, stop vent boosting
_LOGGER.debug(f"low dm or very cold. stopping went boost. dm: {self._hvac.hvac_dm} < {LOW_DEGREE_MINUTES + 100}, temp: {self._hvac.hub.sensors.average_temp_outdoors.value}")
_LOGGER.debug(f"low 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._current_vent_state = False
self._hvac.hub.observer.broadcast("update operation")

Expand All @@ -46,7 +45,7 @@ def _vent_boost_warmth(self) -> bool:
self._hvac.hub.sensors.get_tempdiff() > 3,
self._hvac.hub.sensors.temp_trend_indoors.gradient >= 0,
self._hvac.hub.sensors.temp_trend_outdoors.gradient >= 0,
self._hvac.hub.sensors.average_temp_outdoors.value >= SUMMER_TEMP,
self._hvac.hub.sensors.average_temp_outdoors.value >= self._hvac.hub.options.heating_options.summer_temp,
self._hvac.hub.sensors.set_temp_indoors.preset != HvacPresets.Away,
]
)
Expand All @@ -55,8 +54,8 @@ def _vent_boost_night_cooling(self) -> bool:
return all(
[
self._hvac.hub.sensors.get_tempdiff_in_out() > 4,
self._hvac.hub.sensors.average_temp_outdoors.value >= SUMMER_TEMP,
datetime.now().hour in NIGHT_HOURS,
self._hvac.hub.sensors.average_temp_outdoors.value >= self._hvac.hub.options.heating_options.summer_temp,
datetime.now().hour in self._hvac.hub.options.heating_options.night_hours,
self._hvac.hub.sensors.set_temp_indoors.preset != HvacPresets.Away,
]
)
Expand All @@ -65,8 +64,8 @@ def _vent_boost_night_cooling(self) -> bool:
def _vent_boost_low_dm(self) -> bool:
return all(
[
self._hvac.hvac_dm <= LOW_DEGREE_MINUTES,
self._hvac.hub.sensors.average_temp_outdoors.value >= 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,
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import time
from datetime import datetime, timedelta

import custom_components.peaqhvac.extensionmethods as ex
from peaqevcore.common.trend import Gradient
from custom_components.peaqhvac.service.hvac.interfaces.iheater import IHeater
from peaqevcore.common.wait_timer import WaitTimer
Expand Down Expand Up @@ -31,7 +30,6 @@ def __init__(self, hvac):
self._hvac = hvac
super().__init__(hvac=hvac)
self._current_temp = None
self._boost_non_hours: list[int] = [7, 11, 12, 15, 16, 17] #todo make this an option in the config
self._wait_timer = WaitTimer(timeout=WAITTIMER_TIMEOUT, init_now=False)
self._wait_timer_peak = WaitTimer(timeout=WAITTIMER_TIMEOUT, init_now=False)
self._temp_trend = Gradient(
Expand Down Expand Up @@ -119,12 +117,13 @@ def _get_next_start(self) -> datetime:
return self.booster.next_predicted_demand(
prices_today=self._hvac.hub.nordpool.prices,
prices_tomorrow=self._hvac.hub.nordpool.prices_tomorrow,
min_price=self._hvac.hub.sensors.peaqev_facade.min_price,
demand=DEMAND_MINUTES[preset][demand],
preset=preset,
temp=self.current_temperature,
temp_trend=self._temp_trend.gradient_raw,
target_temp=HIGHTEMP_THRESHOLD,
non_hours=self._boost_non_hours
non_hours=self._hvac.hub.options.heating_options.non_hours_water_boost
)

async def async_update_operation(self, caller=None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def get_demand(temp) -> Demand:
class NextWaterBoost:
def __init__(self):
self.prices = []
self.min_price: float = None # type: ignore
self.groups = []
self.non_hours: list = []
self.demand_hours: dict = {}
Expand All @@ -97,6 +98,7 @@ def next_predicted_demand(
self,
prices_today: list,
prices_tomorrow: list,
min_price: float,
temp: float,
temp_trend: float,
target_temp: float,
Expand All @@ -106,7 +108,9 @@ def next_predicted_demand(
non_hours=None,
high_demand_hours=None
) -> datetime:
self._init_vars(temp,temp_trend, prices_today, prices_tomorrow, preset, non_hours, high_demand_hours, now_dt)
if len(prices_today) < 1:
return datetime.max
self._init_vars(temp,temp_trend, prices_today, prices_tomorrow, preset, min_price, non_hours, high_demand_hours, now_dt)
try:
if target_temp - temp > 0:
delay = 0
Expand All @@ -120,11 +124,12 @@ def next_predicted_demand(
cold=self.current_temp < HIGHTEMP_THRESHOLD
)

def _init_vars(self, temp, temp_trend, prices_today: list, prices_tomorrow: list, preset: HvacPresets, non_hours: list=None, high_demand_hours: dict=None, now_dt=None) -> None:
def _init_vars(self, temp, temp_trend, prices_today: list, prices_tomorrow: list, preset: HvacPresets, min_price: float, non_hours: list=None, high_demand_hours: dict=None, now_dt=None) -> None:
if non_hours is None:
non_hours = []
if high_demand_hours is None:
high_demand_hours = {}
self.min_price = min_price
self.prices = prices_today + prices_tomorrow
self.non_hours = non_hours
self.demand_hours = high_demand_hours
Expand All @@ -143,23 +148,23 @@ def _get_next_start(self, demand: int, delay_dt=None, cold=True) -> datetime:
f"Error on getting last known price with {self.now_dt} and len prices {len(self.prices)}: {e}")
return datetime.max
if last_known_price - self.now_dt > timedelta(hours=18) and not cold:
print("case A")
_LOGGER.debug(f"case A: {last_known_price} - {self.now_dt} > {timedelta(hours=18)}")
#print("case A")
#_LOGGER.debug(f"case A: {last_known_price} - {self.now_dt} > {timedelta(hours=18)}")
group = self._find_group(self.now_dt.hour)
if group.group_type == GroupType.LOW:
return self._calculate_last_start(demand, group.hours)
return self._calculate_last_start(demand)
_next_dt = self._calculate_next_start(demand)
if not delay_dt:
print("case B")
_LOGGER.debug("case B")
#print("case B")
#_LOGGER.debug("case B")
return _next_dt
if _next_dt < delay_dt:
print("case C")
_LOGGER.debug("case C")
#print("case C")
#_LOGGER.debug("case C")
return self._calculate_last_start(demand)
print("case D")
_LOGGER.debug("case D")
#print("case D")
#_LOGGER.debug("case D")
return _next_dt

def _set_now_dt(self, now_dt=None) -> None:
Expand Down Expand Up @@ -196,8 +201,8 @@ def _get_low_period(self, override_dt=None) -> int:

def _values_are_good(self, i) -> bool:
return all([
self.prices[i] < self.floating_mean,
self.prices[i + 1] < self.floating_mean,
self.prices[i] < self.floating_mean or self.prices[i] < self.min_price,
self.prices[i + 1] < self.floating_mean or self.prices[i + 1] < self.min_price,
[i, i + 1, i - 23, i - 24] not in self.non_hours,
])

Expand Down Expand Up @@ -253,6 +258,8 @@ def _group_prices(self, prices_today: list, prices_tomorrow: list) -> None:
today_len = len(prices_today)
std_dev = stdev(self.prices)
average = mean(self.prices)
std_dev_tomorrow: float = None #type: ignore
average_tomorrow: float = None #type:ignore
if len(prices_tomorrow):
std_dev_tomorrow = stdev(prices_tomorrow)
average_tomorrow = mean(prices_tomorrow)
Expand All @@ -262,7 +269,7 @@ def _group_prices(self, prices_today: list, prices_tomorrow: list) -> None:
def __set_group_type(_average, flat, average):
if flat:
return GroupType.FLAT
if _average < average:
if _average < average or _average < self.min_price:
return GroupType.LOW
elif _average > 1.5 * average:
return GroupType.HIGH
Expand Down
11 changes: 11 additions & 0 deletions custom_components/peaqhvac/service/models/config_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@ class MiscOptions:
enabled_on_boot: bool = True


@dataclass
class HeatingOptions:
outdoor_temp_stop_heating: int = 999
non_hours_water_boost: list[int] = field(default_factory=lambda: [])
night_hours: list[int] = field(default_factory=lambda: [])
low_degree_minutes: int = -9999
summer_temp: int = 999
very_cold_temp: int = -999


class ConfigModel:
misc_options: MiscOptions = MiscOptions()
heating_options: HeatingOptions = HeatingOptions()
indoor_tempsensors: List = field(default_factory=lambda: [])
outdoor_tempsensors: List = field(default_factory=lambda: [])
hvacbrand: HvacBrand = field(init=False)
Expand Down

0 comments on commit 1b06cc9

Please sign in to comment.