Skip to content

Commit

Permalink
Allow to have multiple heaters and coolers
Browse files Browse the repository at this point in the history
  • Loading branch information
tubededentifrice committed Jan 1, 2020
1 parent e80b18f commit 3c220cd
Showing 1 changed file with 52 additions and 34 deletions.
86 changes: 52 additions & 34 deletions custom_components/programmable_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HEATER): cv.entity_id,
vol.Optional(CONF_COOLER): cv.entity_id,
vol.Optional(CONF_HEATER): cv.entity_ids,
vol.Optional(CONF_COOLER): cv.entity_ids,
vol.Required(CONF_SENSOR): cv.entity_id,
vol.Required(CONF_TARGET): cv.entity_id,
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
Expand All @@ -60,8 +60,8 @@ async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the generic thermostat platform."""
name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
cooler_entity_id = config.get(CONF_COOLER)
heaters_entity_ids = config.get(CONF_HEATER)
coolers_entity_ids = config.get(CONF_COOLER)
sensor_entity_id = config.get(CONF_SENSOR)
min_temp = config.get(CONF_MIN_TEMP)
max_temp = config.get(CONF_MAX_TEMP)
Expand All @@ -72,21 +72,21 @@ async def async_setup_platform(hass, config, async_add_entities,
unit = hass.config.units.temperature_unit

async_add_entities([ProgrammableThermostat(
hass, name, heater_entity_id, cooler_entity_id, sensor_entity_id, min_temp,
hass, name, heaters_entity_ids, coolers_entity_ids, sensor_entity_id, min_temp,
max_temp, target_entity_id, tolerance, initial_hvac_mode, unit, related_climate)])


class ProgrammableThermostat(ClimateDevice, RestoreEntity):
"""ProgrammableThermostat."""

def __init__(self, hass, name, heater_entity_id, cooler_entity_id,
def __init__(self, hass, name, heaters_entity_ids, coolers_entity_ids,
sensor_entity_id, min_temp, max_temp, target_entity_id,
tolerance, initial_hvac_mode, unit, related_climate):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
self.heater_entity_id = heater_entity_id
self.cooler_entity_id = cooler_entity_id
self.heaters_entity_ids = self._getEntityList(heaters_entity_ids)
self.coolers_entity_ids = self._getEntityList(coolers_entity_ids)
self.sensor_entity_id = sensor_entity_id
self._tolerance = tolerance
self._min_temp = min_temp
Expand All @@ -103,11 +103,11 @@ def __init__(self, hass, name, heater_entity_id, cooler_entity_id,
self._temp_lock = asyncio.Lock()
self._hvac_action = CURRENT_HVAC_OFF

if self.heater_entity_id is not None and self.cooler_entity_id is not None:
if self.heaters_entity_ids is not None and self.coolers_entity_ids is not None:
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF]
elif self.heater_entity_id is not None and self.cooler_entity_id is None:
elif self.heaters_entity_ids is not None and self.coolers_entity_ids is None:
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF]
elif self.cooler_entity_id is not None and self.heater_entity_id is None:
elif self.coolers_entity_ids is not None and self.heaters_entity_ids is None:
self._hvac_list = [HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF]
else:
self._hvac_list = [HVAC_MODE_OFF]
Expand All @@ -131,10 +131,10 @@ async def async_added_to_hass(self):
self.hass, self.sensor_entity_id, self._async_sensor_changed)
if self._hvac_mode == HVAC_MODE_HEAT:
async_track_state_change(
self.hass, self.heater_entity_id, self._async_switch_changed)
self.hass, self.heaters_entity_ids, self._async_switch_changed)
elif self._hvac_mode == HVAC_MODE_COOL:
async_track_state_change(
self.hass, self.cooler_entity_id, self._async_switch_changed)
self.hass, self.coolers_entity_ids, self._async_switch_changed)
async_track_state_change(
self.hass, self.target_entity_id, self._async_target_changed)
if self._related_climate is not None:
Expand Down Expand Up @@ -223,9 +223,9 @@ async def control_system_mode(self):
async def _async_turn_on(self, mode=None):
"""Turn heater toggleable device on."""
if mode == "heat":
data = {ATTR_ENTITY_ID: self.heater_entity_id}
data = {ATTR_ENTITY_ID: self.heaters_entity_ids}
elif mode == "cool":
data = {ATTR_ENTITY_ID: self.cooler_entity_id}
data = {ATTR_ENTITY_ID: self.coolers_entity_ids}
else:
_LOGGER.error("No type has been passed to turn_on function")
self._set_hvac_action_on(mode=mode)
Expand All @@ -240,9 +240,9 @@ async def _async_turn_off(self, mode=None):
_LOGGER.info("Master climate object action is %s, so no action taken.", related_climate_hvac_action)
return
if mode == "heat":
data = {ATTR_ENTITY_ID: self.heater_entity_id}
data = {ATTR_ENTITY_ID: self.heaters_entity_ids}
elif mode == "cool":
data = {ATTR_ENTITY_ID: self.cooler_entity_id}
data = {ATTR_ENTITY_ID: self.coolers_entity_ids}
else:
_LOGGER.error("No type has been passed to turn_off function")
self._set_hvac_action_off(mode=mode)
Expand Down Expand Up @@ -297,14 +297,21 @@ async def _async_target_changed(self, entity_id, old_state, new_state):

async def _async_control_thermo(self, mode=None):
"""Check if we need to turn heating on or off."""
if self._cur_temp is None:
_LOGGER.warn("Abort _async_control_thermo as _cur_temp is None")
return
if self._target_temp is None:
_LOGGER.warn("Abort _async_control_thermo as _target_temp is None")
return

if mode == "heat":
hvac_mode = HVAC_MODE_COOL
delta = self._target_temp - self._cur_temp
entity = self.heater_entity_id
entities = self.heaters_entity_ids
elif mode == "cool":
hvac_mode = HVAC_MODE_HEAT
delta = self._cur_temp - self._target_temp
entity = self.cooler_entity_id
entities = self.coolers_entity_ids
else:
_LOGGER.error("No type has been passed to control_thermo function")
self._check_mode_type = mode
Expand All @@ -319,19 +326,16 @@ async def _async_control_thermo(self, mode=None):
if not self._active or self._hvac_mode == HVAC_MODE_OFF or self._hvac_mode == hvac_mode:
return

if self._is_device_active:
if delta <= 0:
_LOGGER.info("Turning off %s", entity)
self._set_hvac_action_off(mode=mode)
if delta <= 0:
self._set_hvac_action_off(mode=mode)
if not self._areAllInState(entities, STATE_OFF):
_LOGGER.info("Turning off %s", entities)
await self._async_turn_off(mode=mode)
elif delta >= self._tolerance:
self._set_hvac_action_on(mode=mode)
else:
if delta >= self._tolerance:
_LOGGER.info("Turning on %s", entity)
elif delta >= self._tolerance:
self._set_hvac_action_on(mode=mode)
if not self._areAllInState(entities, STATE_ON):
_LOGGER.info("Turning on %s", entities)
await self._async_turn_on(mode=mode)
else:
self._set_hvac_action_off(mode=mode)

def _set_hvac_action_off(self, mode=None):
"""This is used to set CURRENT_HVAC_OFF on the climate integration.
Expand All @@ -354,6 +358,15 @@ def _set_hvac_action_on(self, mode=None):
_LOGGER.error("No type has been passed to turn_on function")
_LOGGER.info("new action %s", self._hvac_action)


def _getEntityList(self, entity_ids):
if entity_ids is not None:
if not isinstance(entity_ids, list):
return [ entity_ids ]
elif len(entity_ids)<=0:
return None
return entity_ids

def _getStateSafe(self, entity_id):
full_state = self.hass.states.get(entity_id)
if full_state is not None:
Expand Down Expand Up @@ -453,18 +466,23 @@ def max_temp(self):
def _is_device_active(self):
"""If the toggleable device is currently active."""
if self._hvac_mode == HVAC_MODE_HEAT:
return self.hass.states.is_state(self.heater_entity_id, STATE_ON)
return self._areAllInState(self.heaters_entity_ids, STATE_ON)
elif self._hvac_mode == HVAC_MODE_COOL:
return self.hass.states.is_state(self.cooler_entity_id, STATE_ON)
return self._areAllInState(self.coolers_entity_ids, STATE_ON)
elif self._hvac_mode == HVAC_MODE_HEAT_COOL:
if self._check_mode_type == "cool":
return self.hass.states.is_state(self.cooler_entity_id, STATE_ON)
return self._areAllInState(self.coolers_entity_ids, STATE_ON)
elif self._check_mode_type == "heat":
return self.hass.states.is_state(self.heater_entity_id, STATE_ON)
return self._areAllInState(self.heaters_entity_ids, STATE_ON)
else:
return False
else:
return False
def _areAllInState(self, entity_ids, state):
for entity_id in entity_ids:
if not self.hass.states.is_state(entity_id, state):
return False
return True

@property
def supported_features(self):
Expand Down

0 comments on commit 3c220cd

Please sign in to comment.