Skip to content

Commit 555fa53

Browse files
committed
Add support for cooling only.
1 parent c2e0e43 commit 555fa53

2 files changed

Lines changed: 53 additions & 23 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,20 @@ gains to quickly test the behavior without waiting the integral to stabilize by
225225
## Parameters:
226226
* **name** (Optional): Name of the thermostat.
227227
* **unique_id** (Optional): unique entity_id for the smart thermostat.
228-
* **heater** (Required): entity_id for heater control, should be a single or list of toggle
228+
* **heater** (Optional): entity_id for heater control, should be a single or list of toggle
229229
device (switch or input_boolean), light or valve (light, number, input_number). If a valve
230230
or a light entity is used, pwm parameter should be set to 0. Becomes air conditioning switch
231231
when ac_mode is set to true.
232232
If you have e.g. multiple radiators in one room, you can enter all of them in a list, and the
233233
thermostat will apply the output value to each of them.
234+
If neither heater nor cooler is specified, the thermostat will not operate and remain stuck in OFF mode.
234235
* **cooler** (Optional): entity_id for cooling control, should be a single or list of toggle
235236
device (switch or input_boolean), light or valve (light, number, input_number). If a valve
236-
or a light is used, pwm parameter should be set to 0. Becomes air conditioning switch when
237-
ac_mode is set to true.
237+
or a light is used, pwm parameter should be set to 0.
238+
If you have e.g. multiple coolers in one room, you can enter all of them in a list, and the
239+
thermostat will apply the output value to each of them.
240+
If both heater and cooler are specified, the thermostat will allow switching between heating and cooling mode.
241+
If neither heater nor cooler is specified, the thermostat will not operate and remain stuck in OFF mode.
238242
* **invert_heater** (Optional): if set to true, inverts the polarity of heater switch (switch is on
239243
while idle and off while active). Must be a boolean (defaults to false).
240244
* **target_sensor** (Required): entity_id for a temperature sensor, target_sensor.state must be

custom_components/smart_thermostat/climate.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
7272
{
73-
vol.Required(const.CONF_HEATER): cv.entity_ids,
73+
vol.Optional(const.CONF_HEATER): cv.entity_ids,
7474
vol.Optional(const.CONF_COOLER): cv.entity_ids,
7575
vol.Required(const.CONF_INVERT_HEATER, default=False): cv.boolean,
7676
vol.Required(const.CONF_SENSOR): cv.entity_id,
@@ -323,14 +323,27 @@ def __init__(self, **kwargs):
323323
self._output_clamp_low = kwargs.get('output_clamp_low')
324324
self._output_clamp_high = kwargs.get('output_clamp_high')
325325
self._difference = self._output_max - self._output_min
326-
if self._ac_mode:
327-
self._attr_hvac_modes = [HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF]
326+
if self._heater_entity_id is not None and self._cooler_entity_id is not None:
327+
# If both heater and cooler are defined, or if ac_mode is enabled, we support both heat and cool modes
328+
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF]
329+
self._min_out = -self._output_clamp_high
330+
self._max_out = self._output_clamp_high
331+
elif self._ac_mode or self._cooler_entity_id is not None:
332+
self._attr_hvac_modes = [HVACMode.COOL, HVACMode.OFF]
328333
self._min_out = -self._output_clamp_high
329334
self._max_out = -self._output_clamp_low
330-
else:
335+
self._ac_mode = True
336+
elif self._heater_entity_id is not None:
331337
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
332338
self._min_out = self._output_clamp_low
333339
self._max_out = self._output_clamp_high
340+
else:
341+
_LOGGER.error("%s: No heater or cooler entity defined, thermostat will not function",
342+
self.entity_id)
343+
self._attr_hvac_modes = [HVACMode.OFF]
344+
self._min_out = 0
345+
self._max_out = 0
346+
self._ac_mode = False
334347
self._kp = kwargs.get('kp')
335348
self._ki = kwargs.get('ki')
336349
self._kd = kwargs.get('kd')
@@ -409,11 +422,12 @@ def _async_startup(*_):
409422
self.hass,
410423
self._ext_sensor_entity_id,
411424
self._async_ext_sensor_changed))
412-
self.async_on_remove(
413-
async_track_state_change_event(
414-
self.hass,
415-
self._heater_entity_id,
416-
self._async_switch_changed))
425+
if self._heater_entity_id is not None:
426+
self.async_on_remove(
427+
async_track_state_change_event(
428+
self.hass,
429+
self._heater_entity_id,
430+
self._async_switch_changed))
417431
if self._cooler_entity_id is not None:
418432
self.async_on_remove(
419433
async_track_state_change_event(
@@ -942,7 +956,15 @@ def heater_or_cooler_entity(self):
942956
"""Return the entity to be controlled based on HVAC MODE"""
943957
if self.hvac_mode == HVACMode.COOL and self._cooler_entity_id is not None:
944958
return self._cooler_entity_id
945-
return self._heater_entity_id
959+
elif self.hvac_mode == HVACMode.HEAT and self._heater_entity_id is not None:
960+
return self._heater_entity_id
961+
else: # Case for OFF
962+
entities_list = []
963+
if self._heater_entity_id is not None:
964+
entities_list.extend(self._heater_entity_id)
965+
if self._cooler_entity_id is not None:
966+
entities_list.extend(self._cooler_entity_id)
967+
return entities_list
946968

947969
async def _async_heater_turn_on(self):
948970
"""Turn heater toggleable device on."""
@@ -982,18 +1004,22 @@ async def _async_heater_turn_off(self, force=False):
9821004
_LOGGER.info("%s: Reject request turning OFF %s: Cycle is too short",
9831005
self.entity_id, ", ".join([entity for entity in self.heater_or_cooler_entity]))
9841006
return
985-
for entity in [self._heater_entity_id, self._cooler_entity_id]:
1007+
entities = []
1008+
if self._heater_entity_id is not None:
1009+
entities.extend(self._heater_entity_id)
1010+
if self._cooler_entity_id is not None:
1011+
entities.extend(self._cooler_entity_id)
1012+
for entity in entities:
9861013
if entity is None:
9871014
continue
988-
for heater_or_cooler_entity in self.heater_or_cooler_entity:
989-
data = {ATTR_ENTITY_ID: heater_or_cooler_entity}
990-
if self._heater_polarity_invert:
991-
service = SERVICE_TURN_ON
992-
else:
993-
service = SERVICE_TURN_OFF
994-
_LOGGER.debug("%s: Calling %s service on %s", self.entity_id, str(service),
995-
str(heater_or_cooler_entity))
996-
await self.hass.services.async_call(HA_DOMAIN, service, data)
1015+
data = {ATTR_ENTITY_ID: entity}
1016+
if self._heater_polarity_invert:
1017+
service = SERVICE_TURN_ON
1018+
else:
1019+
service = SERVICE_TURN_OFF
1020+
_LOGGER.debug("%s: Calling %s service on %s", self.entity_id, str(service),
1021+
str(entity))
1022+
await self.hass.services.async_call(HA_DOMAIN, service, data)
9971023

9981024
async def _async_set_valve_value(self, value: float):
9991025
_LOGGER.info("%s: Change state of %s to %s", self.entity_id,

0 commit comments

Comments
 (0)