|
70 | 70 |
|
71 | 71 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( |
72 | 72 | { |
73 | | - vol.Required(const.CONF_HEATER): cv.entity_ids, |
| 73 | + vol.Optional(const.CONF_HEATER): cv.entity_ids, |
74 | 74 | vol.Optional(const.CONF_COOLER): cv.entity_ids, |
75 | 75 | vol.Required(const.CONF_INVERT_HEATER, default=False): cv.boolean, |
76 | 76 | vol.Required(const.CONF_SENSOR): cv.entity_id, |
@@ -323,14 +323,27 @@ def __init__(self, **kwargs): |
323 | 323 | self._output_clamp_low = kwargs.get('output_clamp_low') |
324 | 324 | self._output_clamp_high = kwargs.get('output_clamp_high') |
325 | 325 | 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] |
328 | 333 | self._min_out = -self._output_clamp_high |
329 | 334 | self._max_out = -self._output_clamp_low |
330 | | - else: |
| 335 | + self._ac_mode = True |
| 336 | + elif self._heater_entity_id is not None: |
331 | 337 | self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] |
332 | 338 | self._min_out = self._output_clamp_low |
333 | 339 | 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 |
334 | 347 | self._kp = kwargs.get('kp') |
335 | 348 | self._ki = kwargs.get('ki') |
336 | 349 | self._kd = kwargs.get('kd') |
@@ -409,11 +422,12 @@ def _async_startup(*_): |
409 | 422 | self.hass, |
410 | 423 | self._ext_sensor_entity_id, |
411 | 424 | 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)) |
417 | 431 | if self._cooler_entity_id is not None: |
418 | 432 | self.async_on_remove( |
419 | 433 | async_track_state_change_event( |
@@ -942,7 +956,15 @@ def heater_or_cooler_entity(self): |
942 | 956 | """Return the entity to be controlled based on HVAC MODE""" |
943 | 957 | if self.hvac_mode == HVACMode.COOL and self._cooler_entity_id is not None: |
944 | 958 | 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 |
946 | 968 |
|
947 | 969 | async def _async_heater_turn_on(self): |
948 | 970 | """Turn heater toggleable device on.""" |
@@ -982,18 +1004,22 @@ async def _async_heater_turn_off(self, force=False): |
982 | 1004 | _LOGGER.info("%s: Reject request turning OFF %s: Cycle is too short", |
983 | 1005 | self.entity_id, ", ".join([entity for entity in self.heater_or_cooler_entity])) |
984 | 1006 | 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: |
986 | 1013 | if entity is None: |
987 | 1014 | 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) |
997 | 1023 |
|
998 | 1024 | async def _async_set_valve_value(self, value: float): |
999 | 1025 | _LOGGER.info("%s: Change state of %s to %s", self.entity_id, |
|
0 commit comments