Skip to content

Commit 63aea6d

Browse files
author
=
committed
feat: heat pump mode (dual with only one switch)
Fixes #143
1 parent 998b9a8 commit 63aea6d

27 files changed

+1825
-238
lines changed

.vscode/settings.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
{
22
"python.pythonPath": "/usr/bin/python3",
3-
"python.formatting.provider": "black",
43
"editor.formatOnSave": true,
54
"python.analysis.diagnosticSeverityOverrides": {},
65
"python.analysis.indexing": true,
76
"python.analysis.autoImportCompletions": true,
87
"python.testing.unittestEnabled": false,
98
"python.testing.pytestEnabled": true,
9+
"[python]": {
10+
"editor.defaultFormatter": "ms-python.black-formatter",
11+
"editor.formatOnSave": true
12+
}
1013
}

config/configuration.yaml

+29
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ input_boolean:
1313
name: Fan toggle
1414
dryer_on:
1515
name: Fan toggle
16+
heat_pump_cool:
17+
name: Heat Pump Heat toggle
1618
window_open:
1719
name: Window
1820
window_open2:
@@ -125,6 +127,17 @@ switch:
125127
data:
126128
entity_id: input_boolean.dryer_on
127129

130+
heat_pump_cool:
131+
value_template: "{{ is_state('input_boolean.heat_pump_cool', 'on') }}"
132+
turn_on:
133+
service: input_boolean.turn_on
134+
data:
135+
entity_id: input_boolean.heat_pump_cool
136+
turn_off:
137+
service: input_boolean.turn_off
138+
data:
139+
entity_id: input_boolean.heat_pump_cool
140+
128141
window:
129142
value_template: "{{ is_state('input_boolean.window_open', 'on') }}"
130143
turn_on:
@@ -465,6 +478,22 @@ climate:
465478
target_temp_low: 18
466479
humidity: 60
467480

481+
- platform: dual_smart_thermostat
482+
name: Dual Heat Pump
483+
unique_id: dual_heat_pump
484+
heater: switch.heater
485+
target_sensor: sensor.room_temp
486+
heat_pump_cooling: switch.heat_pump_cool
487+
heat_cool_mode: true
488+
target_temp_step: 0.1
489+
precision: 0.1
490+
min_temp: 9
491+
max_temp: 32
492+
target_temp: 20
493+
cold_tolerance: 0.3
494+
hot_tolerance: 0.3
495+
496+
468497
# - platform: dual_smart_thermostat
469498
# name: AUX Heat Room
470499
# unique_id: aux_heat_room

custom_components/dual_smart_thermostat/climate.py

+47
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
CONF_FAN_ON_WITH_AC,
103103
CONF_FLOOR_SENSOR,
104104
CONF_HEAT_COOL_MODE,
105+
CONF_HEAT_PUMP_COOLING,
105106
CONF_HEATER,
106107
CONF_HOT_TOLERANCE,
107108
CONF_HUMIDITY_SENSOR,
@@ -187,6 +188,10 @@
187188
vol.Optional(CONF_MOIST_TOLERANCE): vol.Coerce(float),
188189
}
189190

191+
HEAT_PUMP_SCHEMA = {
192+
vol.Optional(CONF_HEAT_PUMP_COOLING): cv.entity_id,
193+
}
194+
190195
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
191196
{
192197
vol.Required(CONF_HEATER): cv.entity_id,
@@ -238,6 +243,8 @@
238243

239244
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(HYGROSTAT_SCHEMA)
240245

246+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(HEAT_PUMP_SCHEMA)
247+
241248
# Add the old presets schema to avoid breaking change
242249
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
243250
{vol.Optional(v): vol.Coerce(float) for (k, v) in CONF_PRESETS_OLD.items()}
@@ -260,6 +267,7 @@ async def async_setup_platform(
260267
sensor_outside_entity_id = config.get(CONF_OUTSIDE_SENSOR)
261268
sensor_humidity_entity_id = config.get(CONF_HUMIDITY_SENSOR)
262269
sensor_stale_duration: timedelta | None = config.get(CONF_STALE_DURATION)
270+
sensor_heat_pump_cooling_entity_id = config.get(CONF_HEAT_PUMP_COOLING)
263271
keep_alive = config.get(CONF_KEEP_ALIVE)
264272

265273
precision = config.get(CONF_PRECISION)
@@ -290,6 +298,7 @@ async def async_setup_platform(
290298
sensor_outside_entity_id,
291299
sensor_humidity_entity_id,
292300
sensor_stale_duration,
301+
sensor_heat_pump_cooling_entity_id,
293302
keep_alive,
294303
precision,
295304
unit,
@@ -343,6 +352,7 @@ def __init__(
343352
sensor_outside_entity_id,
344353
sensor_humidity_entity_id,
345354
sensor_stale_duration,
355+
sensor_heat_pump_cooling_entity_id,
346356
keep_alive,
347357
precision,
348358
unit,
@@ -378,6 +388,7 @@ def __init__(
378388
self.sensor_floor_entity_id = sensor_floor_entity_id
379389
self.sensor_outside_entity_id = sensor_outside_entity_id
380390
self.sensor_humidity_entity_id = sensor_humidity_entity_id
391+
self.sensor_heat_pump_cooling_entity_id = sensor_heat_pump_cooling_entity_id
381392

382393
self._keep_alive = keep_alive
383394

@@ -473,6 +484,19 @@ async def async_added_to_hass(self) -> None:
473484
)
474485
)
475486

487+
if self.sensor_heat_pump_cooling_entity_id is not None:
488+
_LOGGER.debug(
489+
"Adding heat pump cooling sensor listener: %s",
490+
self.sensor_heat_pump_cooling_entity_id,
491+
)
492+
self.async_on_remove(
493+
async_track_state_change_event(
494+
self.hass,
495+
[self.sensor_heat_pump_cooling_entity_id],
496+
self._async_entity_heat_pump_cooling_changed_event,
497+
)
498+
)
499+
476500
if self._keep_alive:
477501
self.async_on_remove(
478502
async_track_time_interval(
@@ -868,6 +892,8 @@ def _set_temperatures_dual_mode(self, temperatures: TargetTemperatures) -> None:
868892
temp_low = temperatures.temp_low
869893
temp_high = temperatures.temp_high
870894

895+
self.hvac_device.on_target_temperature_change(temperatures)
896+
871897
if self.features.is_target_mode:
872898
if temperature is None:
873899
return
@@ -1028,6 +1054,27 @@ async def _async_sensor_humidity_changed(
10281054
await self._async_control_climate()
10291055
self.async_write_ha_state()
10301056

1057+
async def _async_entity_heat_pump_cooling_changed_event(
1058+
self, event: Event[EventStateChangedData]
1059+
) -> None:
1060+
data = event.data
1061+
1062+
self.hvac_device.on_entity_state_changed(data["entity_id"], data["new_state"])
1063+
1064+
await self._asyn_entity_heat_pump_cooling_changed(data["new_state"])
1065+
self._attr_hvac_modes = self.hvac_device.hvac_modes
1066+
self.async_write_ha_state()
1067+
1068+
async def _asyn_entity_heat_pump_cooling_changed(
1069+
self, new_state: State | None, trigger_control=True
1070+
) -> None:
1071+
"""Handle heat pump cooling changes."""
1072+
_LOGGER.info("Entity heat pump cooling change: %s", new_state)
1073+
1074+
if trigger_control:
1075+
await self._async_control_climate()
1076+
self.async_write_ha_state()
1077+
10311078
async def _check_device_initial_state(self) -> None:
10321079
"""Prevent the device from keep running if HVACMode.OFF."""
10331080
_LOGGER.debug("Checking device initial state")

custom_components/dual_smart_thermostat/const.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
CONF_OPENINGS = "openings"
6363
CONF_OPENINGS_SCOPE = "openings_scope"
6464
CONF_HEAT_COOL_MODE = "heat_cool_mode"
65+
CONF_HEAT_PUMP_COOLING = "heat_pump_cooling"
6566

6667
ATTR_PREV_TARGET = "prev_target_temp"
6768
ATTR_PREV_TARGET_LOW = "prev_target_temp_low"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Hvac Action Reason Module"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""HVAC controller module for Dual Smart Thermostat."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from datetime import timedelta
2+
import logging
3+
from typing import Callable
4+
5+
from homeassistant.core import HomeAssistant
6+
7+
from custom_components.dual_smart_thermostat.hvac_controller.generic_controller import (
8+
GenericHvacController,
9+
)
10+
from custom_components.dual_smart_thermostat.managers.environment_manager import (
11+
EnvironmentManager,
12+
)
13+
from custom_components.dual_smart_thermostat.managers.opening_manager import (
14+
OpeningManager,
15+
)
16+
17+
_LOGGER = logging.getLogger(__name__)
18+
19+
20+
class CoolerHvacController(GenericHvacController):
21+
22+
def __init__(
23+
self,
24+
hass: HomeAssistant,
25+
entity_id,
26+
min_cycle_duration: timedelta,
27+
environment: EnvironmentManager,
28+
openings: OpeningManager,
29+
turn_on_callback: Callable,
30+
turn_off_callback: Callable,
31+
) -> None:
32+
self._controller_type = self.__class__.__name__
33+
34+
super().__init__(
35+
hass,
36+
entity_id,
37+
min_cycle_duration,
38+
environment,
39+
openings,
40+
turn_on_callback,
41+
turn_off_callback,
42+
)

0 commit comments

Comments
 (0)