Skip to content

Commit

Permalink
[TASK] implement main cooler control
Browse files Browse the repository at this point in the history
  • Loading branch information
KartoffelToby committed Sep 21, 2023
1 parent 6c60274 commit e898055
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 31 deletions.
7 changes: 7 additions & 0 deletions .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ climate:
heater: input_boolean.heater2
target_sensor: input_number.internal_sensor2

- platform: generic_thermostat
name: Dummy_real_AC
heater: input_boolean.heater2
target_sensor: input_number.internal_sensor2
ac_mode: true
cold_tolerance: 0.3

input_boolean:
heater:
name: Heater
Expand Down
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
},
"python.formatting.provider": "none"
}
150 changes: 134 additions & 16 deletions custom_components/better_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from random import randint
from statistics import mean

from custom_components.better_thermostat.events.cooler import trigger_cooler_change

from .utils.watcher import check_all_entities

from .utils.weather import check_ambient_air_temperature, check_weather
Expand All @@ -20,17 +22,23 @@

from .utils.model_quirks import load_model_quirks

from .utils.helpers import convert_to_float, find_battery_entity
from .utils.helpers import convert_to_float, find_battery_entity, get_hvac_bt_mode
from homeassistant.helpers import entity_platform
from homeassistant.core import callback, CoreState, Context, ServiceCall
import json
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate import (
ClimateEntity,
ATTR_HVAC_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
)
from homeassistant.components.climate.const import (
ATTR_MAX_TEMP,
ATTR_MIN_TEMP,
ATTR_TARGET_TEMP_STEP,
HVACMode,
HVACAction,
ClimateEntityFeature,
)
from homeassistant.const import (
CONF_NAME,
Expand Down Expand Up @@ -58,6 +66,7 @@
ATTR_STATE_WINDOW_OPEN,
ATTR_STATE_SAVED_TEMPERATURE,
ATTR_STATE_HEATING_POWER,
CONF_COOLER,
CONF_HEATER,
CONF_HUMIDITY,
CONF_MODEL,
Expand Down Expand Up @@ -135,6 +144,7 @@ async def async_service_handler(self, data: ServiceCall):
entry.data.get(CONF_OFF_TEMPERATURE, None),
entry.data.get(CONF_TOLERANCE, 0.0),
entry.data.get(CONF_MODEL, None),
entry.data.get(CONF_COOLER, None),
hass.config.units.temperature_unit,
entry.entry_id,
device_class="better_thermostat",
Expand Down Expand Up @@ -203,6 +213,7 @@ def __init__(
off_temperature,
tolerance,
model,
cooler_entity_id,
unit,
unique_id,
device_class,
Expand All @@ -221,6 +232,7 @@ def __init__(
self.all_trvs = heater_entity_id
self.sensor_entity_id = sensor_entity_id
self.humidity_entity_id = humidity_sensor_entity_id
self.cooler_entity_id = cooler_entity_id
self.window_id = window_id or None
self.window_delay = window_delay or 0
self.window_delay_after = window_delay_after or 0
Expand All @@ -232,7 +244,8 @@ def __init__(
self._unit = unit
self._device_class = device_class
self._state_class = state_class
self._hvac_list = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF]
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
self.map_on_hvac_mode = HVACMode.HEAT
self.next_valve_maintenance = datetime.now() + timedelta(
hours=randint(1, 24 * 5)
)
Expand All @@ -243,6 +256,7 @@ def __init__(
self.bt_min_temp = 0
self.bt_max_temp = 30
self.bt_target_temp = 5
self.bt_target_cooltemp = None
self._support_flags = SUPPORT_FLAGS
self.bt_hvac_mode = None
self.closed_window_triggered = False
Expand Down Expand Up @@ -295,6 +309,11 @@ async def async_added_to_hass(self):
"You updated from version before 1.0.0-Beta36 of the Better Thermostat integration, you need to remove the BT devices (integration) and add it again."
)

if self.cooler_entity_id is not None:
self._hvac_list.remove(HVACMode.HEAT)
self._hvac_list.append(HVACMode.HEAT_COOL)
self.map_on_hvac_mode = HVACMode.HEAT_COOL

self.entity_ids = [
entity for trv in self.all_trvs if (entity := trv["trv"]) is not None
]
Expand Down Expand Up @@ -426,6 +445,16 @@ async def _trigger_window_change(self, event):

self.hass.async_create_task(trigger_window_change(self, event))

async def _tigger_cooler_change(self, event):
_check = await check_all_entities(self)
if _check is False:
return
self.async_set_context(event.context)
if (event.data.get("new_state")) is None:
return

self.hass.async_create_task(trigger_cooler_change(self, event))

async def startup(self):
"""Run when entity about to be added.
Expand All @@ -439,6 +468,7 @@ async def startup(self):
self.name,
self.version,
)

sensor_state = self.hass.states.get(self.sensor_entity_id)
if sensor_state is not None:
if sensor_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
Expand Down Expand Up @@ -487,6 +517,20 @@ async def startup(self):
await asyncio.sleep(10)
continue

if self.cooler_entity_id is not None:
if self.hass.states.get(self.cooler_entity_id).state in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
None,
):
_LOGGER.info(
"better_thermostat %s: waiting for cooler entity with id '%s' to become fully available...",
self.name,
self.cooler_entity_id,
)
await asyncio.sleep(10)
continue

if self.humidity_entity_id is not None:
if self.hass.states.get(self.humidity_entity_id).state in (
STATE_UNAVAILABLE,
Expand Down Expand Up @@ -553,6 +597,18 @@ async def startup(self):
self.name,
"startup()",
)

if self.cooler_entity_id is not None:
self.bt_target_cooltemp = convert_to_float(
str(
self.hass.states.get(self.cooler_entity_id).attributes.get(
"temperature"
)
),
self.name,
"startup()",
)

if self.window_id is not None:
self.all_entities.append(self.window_id)
window = self.hass.states.get(self.window_id)
Expand Down Expand Up @@ -702,7 +758,11 @@ async def startup(self):
self.cur_humidity = 0

self.last_window_state = self.window_open
if self.bt_hvac_mode not in (HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT):
if self.bt_hvac_mode not in (
HVACMode.OFF,
HVACMode.HEAT_COOL,
HVACMode.HEAT,
):
self.bt_hvac_mode = HVACMode.HEAT

self.async_write_ha_state()
Expand Down Expand Up @@ -830,6 +890,12 @@ async def startup(self):
self.hass, [self.window_id], self._trigger_window_change
)
)
if self.cooler_entity_id is not None:
self.async_on_remove(
async_track_state_change_event(
self.hass, [self.cooler_entity_id], self._tigger_cooler_change
)
)
_LOGGER.info("better_thermostat %s: startup completed.", self.name)
self.async_write_ha_state()
await self.async_update_ha_state(force_refresh=True)
Expand Down Expand Up @@ -1024,7 +1090,7 @@ def hvac_mode(self):
string
HVAC mode only from homeassistant.components.climate.const is valid
"""
return self.bt_hvac_mode
return get_hvac_bt_mode(self, self.bt_hvac_mode)

@property
def hvac_action(self):
Expand All @@ -1041,7 +1107,7 @@ def hvac_action(self):
return self.attr_hvac_action

@property
def target_temperature(self):
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.
Returns
Expand All @@ -1059,6 +1125,18 @@ def target_temperature(self):
return self.bt_max_temp
return self.bt_target_temp

@property
def target_temperature_low(self) -> float | None:
if self.cooler_entity_id is None:
return None
return self.bt_target_temp

@property
def target_temperature_high(self) -> float | None:
if self.cooler_entity_id is None:
return None
return self.bt_target_cooltemp

@property
def hvac_modes(self):
"""List of available operation modes.
Expand All @@ -1077,8 +1155,8 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None:
-------
None
"""
if hvac_mode in (HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF):
self.bt_hvac_mode = hvac_mode
if hvac_mode in (HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF):
self.bt_hvac_mode = get_hvac_bt_mode(self, hvac_mode)
else:
_LOGGER.error(
"better_thermostat %s: Unsupported hvac_mode %s", self.name, hvac_mode
Expand All @@ -1098,17 +1176,55 @@ async def async_set_temperature(self, **kwargs) -> None:
-------
None
"""
_new_setpoint = convert_to_float(
str(kwargs.get(ATTR_TEMPERATURE, None)),
self.name,
"controlling.settarget_temperature()",
)
if _new_setpoint is None:
_new_setpoint = None
_new_setpointlow = None
_new_setpointhigh = None

if ATTR_HVAC_MODE in kwargs:
hvac_mode = str(kwargs.get(ATTR_HVAC_MODE, None))
if hvac_mode in (HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF):
self.bt_hvac_mode = hvac_mode
else:
_LOGGER.error(
"better_thermostat %s: Unsupported hvac_mode %s",
self.name,
hvac_mode,
)
if ATTR_TEMPERATURE in kwargs:
_new_setpoint = convert_to_float(
str(kwargs.get(ATTR_TEMPERATURE, None)),
self.name,
"controlling.settarget_temperature()",
)
if ATTR_TARGET_TEMP_LOW in kwargs:
_new_setpointlow = convert_to_float(
str(kwargs.get(ATTR_TARGET_TEMP_LOW, None)),
self.name,
"controlling.settarget_temperature_low()",
)
if ATTR_TARGET_TEMP_HIGH in kwargs:
_new_setpointhigh = convert_to_float(
str(kwargs.get(ATTR_TARGET_TEMP_HIGH, None)),
self.name,
"controlling.settarget_temperature_high()",
)

if _new_setpoint is None and _new_setpointlow is None:
_LOGGER.debug(
f"better_thermostat {self.name}: received a new setpoint from HA, but temperature attribute was not set, ignoring"
)
return
self.bt_target_temp = _new_setpoint
self.bt_target_temp = _new_setpoint or _new_setpointlow
if _new_setpointhigh is not None:
self.bt_target_cooltemp = _new_setpointhigh

_LOGGER.debug(
"better_thermostat %s: HA set target temperature to %s & %s",
self.name,
self.bt_target_temp,
self.bt_target_cooltemp,
)

self.async_write_ha_state()
await self.control_queue_task.put(self)

Expand Down Expand Up @@ -1166,4 +1282,6 @@ def supported_features(self):
array
Supported features.
"""
return self._support_flags
if self.cooler_entity_id is not None:
return ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
return ClimateEntityFeature.TARGET_TEMPERATURE
16 changes: 11 additions & 5 deletions custom_components/better_thermostat/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .utils.helpers import get_device_model, get_trv_intigration

from .const import (
CONF_COOLER,
CONF_PROTECT_OVERHEATING,
CONF_CALIBRATION,
CONF_CHILD_LOCK,
Expand Down Expand Up @@ -253,6 +254,8 @@ async def async_step_user(self, user_input=None):
self.data[CONF_OUTDOOR_SENSOR] = None
if CONF_WEATHER not in self.data:
self.data[CONF_WEATHER] = None
if CONF_COOLER not in self.data:
self.data[CONF_COOLER] = None

if CONF_WINDOW_TIMEOUT in self.data:
self.data[CONF_WINDOW_TIMEOUT] = (
Expand Down Expand Up @@ -302,6 +305,9 @@ async def async_step_user(self, user_input=None):
vol.Required(CONF_HEATER): selector.EntitySelector(
selector.EntitySelectorConfig(domain="climate", multiple=True)
),
vol.Required(CONF_COOLER): selector.EntitySelector(
selector.EntitySelectorConfig(domain="climate", multiple=False)
),
vol.Required(CONF_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "number", "input_number"],
Expand Down Expand Up @@ -346,8 +352,7 @@ async def async_step_user(self, user_input=None):
default=user_input.get(CONF_OFF_TEMPERATURE, 20),
): int,
vol.Optional(
CONF_TOLERANCE,
default=user_input.get(CONF_TOLERANCE, 0.0),
CONF_TOLERANCE, default=user_input.get(CONF_TOLERANCE, 0.0)
): float,
}
),
Expand Down Expand Up @@ -548,7 +553,9 @@ async def async_step_user(self, user_input=None):
CONF_OFF_TEMPERATURE
)

self.updated_config[CONF_TOLERANCE] = user_input.get(CONF_TOLERANCE, 0.0)
self.updated_config[CONF_TOLERANCE] = float(
user_input.get(CONF_TOLERANCE, 0.0)
)

for trv in self.updated_config[CONF_HEATER]:
trv["adapter"] = None
Expand Down Expand Up @@ -673,8 +680,7 @@ async def async_step_user(self, user_input=None):

fields[
vol.Optional(
CONF_TOLERANCE,
default=self.config_entry.data.get(CONF_TOLERANCE, 0.0),
CONF_TOLERANCE, default=self.config_entry.data.get(CONF_TOLERANCE, 0.0)
)
] = float

Expand Down
1 change: 1 addition & 0 deletions custom_components/better_thermostat/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@


CONF_HEATER = "thermostat"
CONF_COOLER = "cooler"
CONF_SENSOR = "temperature_sensor"
CONF_HUMIDITY = "humidity_sensor"
CONF_SENSOR_WINDOW = "window_sensors"
Expand Down
Loading

0 comments on commit e898055

Please sign in to comment.