Skip to content

Commit

Permalink
feat: heat pump mode (dual with only one switch)
Browse files Browse the repository at this point in the history
Fixes #143
  • Loading branch information
= committed Jul 23, 2024
1 parent 998b9a8 commit c097138
Show file tree
Hide file tree
Showing 28 changed files with 1,882 additions and 238 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"python.pythonPath": "/usr/bin/python3",
"python.formatting.provider": "black",
"editor.formatOnSave": true,
"python.analysis.diagnosticSeverityOverrides": {},
"python.analysis.indexing": true,
"python.analysis.autoImportCompletions": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
}
}
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The `dual_smart_thermostat` is an enhanced version of generic thermostat impleme
| **Fan With Cooler mode** | ![fan](/docs/images/fan-custom.png) ![cool](/docs/images/snowflake-custom.png) | [docs](#fan-with-cooler-mode) |
| **Cooler Only mode** | ![cool](/docs/images/snowflake-custom.png) | [docs](#cooler-only-mode) |
| **Dry mode** | ![humidity](docs/images/water-percent-custom.png) | [docs](#dry-mode) |
| **Heat Pump mode** | ![haet/cool](docs/images/sun-snowflake-custom.png) | [docs](#heat-pump-one-switch-heatcool-mode) |
| **Floor Temperature Control** | ![heating-coil](docs/images/heating-coil-custom.png) ![snowflake-thermometer](docs/images/snowflake-thermometer-custom.png) ![thermometer-alert](docs/images/thermometer-alert-custom.png) | [docs](#floor-heating-temperature-control) |
| **Window/Door sensor integration** | ![window-open](docs/images/window-open-custom.png) ![window-open](docs/images/door-open-custom.png) ![chevron-right](docs/images/chevron-right-custom.png) ![timer-cog](docs/images/timer-cog-outline-custom.png) ![chevron-right](docs/images/chevron-right-custom.png) ![hvac-off](docs/images/hvac-off-custom.png)| [docs](#openings) |
| **Presets** | | [docs](#presets) |
Expand Down Expand Up @@ -155,6 +156,57 @@ moist_tolerance: 5
dry_tolerance: 5
```

### Heat Pump (one switch heat/cool) mode

This setup allows you to use a single switch for both heating and cooling. To enable this mode you define only a single switch for the heater and set the set youer heat pump's current state (heating or cooling) as for the [`heat_pump_cooling`](#heat_pump_cooling) attribute. This must be an entity id of a sensor that has a state of `heating` or `cooling`.

The entity can be an input buulean for manual control or an entity that provided by the heat pump.

```yaml
heater: switch.study_heat_pump
target_sensor: sensor.study_temperature
heat_pump_cooling: sensor.study_heat_pump_state
```

#### Heat Pump Hvac Modes

##### Heat-Cool Mode

```yaml
heater: switch.study_heat_pump
target_sensor: sensor.study_temperature
heat_pump_cooling: sensor.study_heat_pump_state
heat_cool_mode: true
```

**heating** _(heat_pump_cooling: false)_:
- heat/cool
- heat
- off

**cooling** _(heat_pump_cooling: true)_:
- heat/cool
- cool
- off

##### Single mode

```yaml
heater: switch.study_heat_pump
target_sensor: sensor.study_temperature
heat_pump_cooling: sensor.study_heat_pump_state
heat_cool_mode: false # <-- or not set
```

**heating** _(heat_pump_cooling: false)_:
- heat
- off

**cooling** _(heat_pump_cooling: true)_:
- cool
- off


## Openings

The `dual_smart_thermostat` can turn off heating or cooling if a window or door is opened and turn heating or cooling back on when the door or window is closed to save energy.
Expand Down Expand Up @@ -439,6 +491,11 @@ The internal values can be set by the component only and the external values can
- `heat_cool`
- `fan_only`

### heat_pump_cooling

_(optional) (string)_ "`entity_id` for the heat pump cooling state sensor, heat_pump_cooling.state must be `heating` or `cooling`."
enables [heat pump mode](#heat-pump-one-switch-heatcool-mode)

### min_temp

_(optional) (float)_
Expand Down
29 changes: 29 additions & 0 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ input_boolean:
name: Fan toggle
dryer_on:
name: Fan toggle
heat_pump_cool:
name: Heat Pump Heat toggle
window_open:
name: Window
window_open2:
Expand Down Expand Up @@ -125,6 +127,17 @@ switch:
data:
entity_id: input_boolean.dryer_on

heat_pump_cool:
value_template: "{{ is_state('input_boolean.heat_pump_cool', 'on') }}"
turn_on:
service: input_boolean.turn_on
data:
entity_id: input_boolean.heat_pump_cool
turn_off:
service: input_boolean.turn_off
data:
entity_id: input_boolean.heat_pump_cool

window:
value_template: "{{ is_state('input_boolean.window_open', 'on') }}"
turn_on:
Expand Down Expand Up @@ -465,6 +478,22 @@ climate:
target_temp_low: 18
humidity: 60

- platform: dual_smart_thermostat
name: Dual Heat Pump
unique_id: dual_heat_pump
heater: switch.heater
target_sensor: sensor.room_temp
heat_pump_cooling: switch.heat_pump_cool
heat_cool_mode: true
target_temp_step: 0.1
precision: 0.1
min_temp: 9
max_temp: 32
target_temp: 20
cold_tolerance: 0.3
hot_tolerance: 0.3


# - platform: dual_smart_thermostat
# name: AUX Heat Room
# unique_id: aux_heat_room
Expand Down
47 changes: 47 additions & 0 deletions custom_components/dual_smart_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
CONF_FAN_ON_WITH_AC,
CONF_FLOOR_SENSOR,
CONF_HEAT_COOL_MODE,
CONF_HEAT_PUMP_COOLING,
CONF_HEATER,
CONF_HOT_TOLERANCE,
CONF_HUMIDITY_SENSOR,
Expand Down Expand Up @@ -187,6 +188,10 @@
vol.Optional(CONF_MOIST_TOLERANCE): vol.Coerce(float),
}

HEAT_PUMP_SCHEMA = {
vol.Optional(CONF_HEAT_PUMP_COOLING): cv.entity_id,
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HEATER): cv.entity_id,
Expand Down Expand Up @@ -238,6 +243,8 @@

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(HYGROSTAT_SCHEMA)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(HEAT_PUMP_SCHEMA)

# Add the old presets schema to avoid breaking change
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{vol.Optional(v): vol.Coerce(float) for (k, v) in CONF_PRESETS_OLD.items()}
Expand All @@ -260,6 +267,7 @@ async def async_setup_platform(
sensor_outside_entity_id = config.get(CONF_OUTSIDE_SENSOR)
sensor_humidity_entity_id = config.get(CONF_HUMIDITY_SENSOR)
sensor_stale_duration: timedelta | None = config.get(CONF_STALE_DURATION)
sensor_heat_pump_cooling_entity_id = config.get(CONF_HEAT_PUMP_COOLING)
keep_alive = config.get(CONF_KEEP_ALIVE)

precision = config.get(CONF_PRECISION)
Expand Down Expand Up @@ -290,6 +298,7 @@ async def async_setup_platform(
sensor_outside_entity_id,
sensor_humidity_entity_id,
sensor_stale_duration,
sensor_heat_pump_cooling_entity_id,
keep_alive,
precision,
unit,
Expand Down Expand Up @@ -343,6 +352,7 @@ def __init__(
sensor_outside_entity_id,
sensor_humidity_entity_id,
sensor_stale_duration,
sensor_heat_pump_cooling_entity_id,
keep_alive,
precision,
unit,
Expand Down Expand Up @@ -378,6 +388,7 @@ def __init__(
self.sensor_floor_entity_id = sensor_floor_entity_id
self.sensor_outside_entity_id = sensor_outside_entity_id
self.sensor_humidity_entity_id = sensor_humidity_entity_id
self.sensor_heat_pump_cooling_entity_id = sensor_heat_pump_cooling_entity_id

self._keep_alive = keep_alive

Expand Down Expand Up @@ -473,6 +484,19 @@ async def async_added_to_hass(self) -> None:
)
)

if self.sensor_heat_pump_cooling_entity_id is not None:
_LOGGER.debug(
"Adding heat pump cooling sensor listener: %s",
self.sensor_heat_pump_cooling_entity_id,
)
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self.sensor_heat_pump_cooling_entity_id],
self._async_entity_heat_pump_cooling_changed_event,
)
)

if self._keep_alive:
self.async_on_remove(
async_track_time_interval(
Expand Down Expand Up @@ -868,6 +892,8 @@ def _set_temperatures_dual_mode(self, temperatures: TargetTemperatures) -> None:
temp_low = temperatures.temp_low
temp_high = temperatures.temp_high

self.hvac_device.on_target_temperature_change(temperatures)

if self.features.is_target_mode:
if temperature is None:
return
Expand Down Expand Up @@ -1028,6 +1054,27 @@ async def _async_sensor_humidity_changed(
await self._async_control_climate()
self.async_write_ha_state()

async def _async_entity_heat_pump_cooling_changed_event(
self, event: Event[EventStateChangedData]
) -> None:
data = event.data

self.hvac_device.on_entity_state_changed(data["entity_id"], data["new_state"])

await self._asyn_entity_heat_pump_cooling_changed(data["new_state"])
self._attr_hvac_modes = self.hvac_device.hvac_modes
self.async_write_ha_state()

async def _asyn_entity_heat_pump_cooling_changed(
self, new_state: State | None, trigger_control=True
) -> None:
"""Handle heat pump cooling changes."""
_LOGGER.info("Entity heat pump cooling change: %s", new_state)

if trigger_control:
await self._async_control_climate()
self.async_write_ha_state()

async def _check_device_initial_state(self) -> None:
"""Prevent the device from keep running if HVACMode.OFF."""
_LOGGER.debug("Checking device initial state")
Expand Down
1 change: 1 addition & 0 deletions custom_components/dual_smart_thermostat/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
CONF_OPENINGS = "openings"
CONF_OPENINGS_SCOPE = "openings_scope"
CONF_HEAT_COOL_MODE = "heat_cool_mode"
CONF_HEAT_PUMP_COOLING = "heat_pump_cooling"

ATTR_PREV_TARGET = "prev_target_temp"
ATTR_PREV_TARGET_LOW = "prev_target_temp_low"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Hvac Action Reason Module"""
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""HVAC controller module for Dual Smart Thermostat."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from datetime import timedelta
import logging
from typing import Callable

from homeassistant.core import HomeAssistant

from custom_components.dual_smart_thermostat.hvac_controller.generic_controller import (
GenericHvacController,
)
from custom_components.dual_smart_thermostat.managers.environment_manager import (
EnvironmentManager,
)
from custom_components.dual_smart_thermostat.managers.opening_manager import (
OpeningManager,
)

_LOGGER = logging.getLogger(__name__)


class CoolerHvacController(GenericHvacController):

def __init__(
self,
hass: HomeAssistant,
entity_id,
min_cycle_duration: timedelta,
environment: EnvironmentManager,
openings: OpeningManager,
turn_on_callback: Callable,
turn_off_callback: Callable,
) -> None:
self._controller_type = self.__class__.__name__

super().__init__(
hass,
entity_id,
min_cycle_duration,
environment,
openings,
turn_on_callback,
turn_off_callback,
)
Loading

0 comments on commit c097138

Please sign in to comment.