diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index 3f1d9a208ac..307c96bce32 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -123,6 +123,7 @@ class GenericThermostat(ClimateDevice): self._enabled = True self._active = False self._cur_temp = None + self._temp_lock = asyncio.Lock() self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp @@ -140,7 +141,7 @@ class GenericThermostat(ClimateDevice): if self._keep_alive: async_track_time_interval( - hass, self._async_keep_alive, self._keep_alive) + hass, self._async_control_heating, self._keep_alive) sensor_state = hass.states.get(sensor_entity_id) if sensor_state and sensor_state.state != STATE_UNKNOWN: @@ -234,31 +235,30 @@ class GenericThermostat(ClimateDevice): if operation_mode == STATE_HEAT: self._current_operation = STATE_HEAT self._enabled = True - self._async_control_heating() + await self._async_control_heating() elif operation_mode == STATE_COOL: self._current_operation = STATE_COOL self._enabled = True - self._async_control_heating() + await self._async_control_heating() elif operation_mode == STATE_OFF: self._current_operation = STATE_OFF self._enabled = False if self._is_device_active: - self._heater_turn_off() + await self._async_heater_turn_off() else: _LOGGER.error("Unrecognized operation mode: %s", operation_mode) return # Ensure we update the current operation after changing the mode self.schedule_update_ha_state() - @asyncio.coroutine - def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: return self._target_temp = temperature - self._async_control_heating() - yield from self.async_update_ha_state() + await self._async_control_heating() + await self.async_update_ha_state() @property def min_temp(self): @@ -278,15 +278,14 @@ class GenericThermostat(ClimateDevice): # Get default temp from super class return super().max_temp - @asyncio.coroutine - def _async_sensor_changed(self, entity_id, old_state, new_state): + async def _async_sensor_changed(self, entity_id, old_state, new_state): """Handle temperature changes.""" if new_state is None: return self._async_update_temp(new_state) - self._async_control_heating() - yield from self.async_update_ha_state() + await self._async_control_heating() + await self.async_update_ha_state() @callback def _async_switch_changed(self, entity_id, old_state, new_state): @@ -295,14 +294,6 @@ class GenericThermostat(ClimateDevice): return self.async_schedule_update_ha_state() - @callback - def _async_keep_alive(self, time): - """Call at constant intervals for keep-alive purposes.""" - if self._is_device_active: - self._heater_turn_on() - else: - self._heater_turn_off() - @callback def _async_update_temp(self, state): """Update thermostat with latest state from sensor.""" @@ -314,62 +305,51 @@ class GenericThermostat(ClimateDevice): except ValueError as ex: _LOGGER.error("Unable to update from sensor: %s", ex) - @callback - def _async_control_heating(self): + async def _async_control_heating(self, time=None): """Check if we need to turn heating on or off.""" - if not self._active and None not in (self._cur_temp, - self._target_temp): - self._active = True - _LOGGER.info("Obtained current and target temperature. " - "Generic thermostat active. %s, %s", - self._cur_temp, self._target_temp) + async with self._temp_lock: + if not self._active and None not in (self._cur_temp, + self._target_temp): + self._active = True + _LOGGER.info("Obtained current and target temperature. " + "Generic thermostat active. %s, %s", + self._cur_temp, self._target_temp) - if not self._active: - return - - if not self._enabled: - return - - if self.min_cycle_duration: - if self._is_device_active: - current_state = STATE_ON - else: - current_state = STATE_OFF - long_enough = condition.state( - self.hass, self.heater_entity_id, current_state, - self.min_cycle_duration) - if not long_enough: + if not self._active or not self._enabled: return - if self.ac_mode: - is_cooling = self._is_device_active - if is_cooling: - too_cold = self._target_temp - self._cur_temp >= \ - self._cold_tolerance - if too_cold: - _LOGGER.info("Turning off AC %s", self.heater_entity_id) - self._heater_turn_off() - else: - too_hot = self._cur_temp - self._target_temp >= \ - self._hot_tolerance - if too_hot: - _LOGGER.info("Turning on AC %s", self.heater_entity_id) - self._heater_turn_on() - else: - is_heating = self._is_device_active - if is_heating: - too_hot = self._cur_temp - self._target_temp >= \ - self._hot_tolerance - if too_hot: + if self.min_cycle_duration: + if self._is_device_active: + current_state = STATE_ON + else: + current_state = STATE_OFF + long_enough = condition.state( + self.hass, self.heater_entity_id, current_state, + self.min_cycle_duration) + if not long_enough: + return + + too_cold = \ + self._target_temp - self._cur_temp >= self._cold_tolerance + too_hot = \ + self._cur_temp - self._target_temp >= self._hot_tolerance + if self._is_device_active: + if (self.ac_mode and too_cold) or \ + (not self.ac_mode and too_hot): _LOGGER.info("Turning off heater %s", self.heater_entity_id) - self._heater_turn_off() + await self._async_heater_turn_off() + elif time is not None: + # The time argument is passed only in keep-alive case + await self._async_heater_turn_on() else: - too_cold = self._target_temp - self._cur_temp >= \ - self._cold_tolerance - if too_cold: + if (self.ac_mode and too_hot) or \ + (not self.ac_mode and too_cold): _LOGGER.info("Turning on heater %s", self.heater_entity_id) - self._heater_turn_on() + await self._async_heater_turn_on() + elif time is not None: + # The time argument is passed only in keep-alive case + await self._async_heater_turn_off() @property def _is_device_active(self): @@ -381,36 +361,32 @@ class GenericThermostat(ClimateDevice): """Return the list of supported features.""" return self._support_flags - @callback - def _heater_turn_on(self): + async def _async_heater_turn_on(self): """Turn heater toggleable device on.""" data = {ATTR_ENTITY_ID: self.heater_entity_id} - self.hass.async_add_job( - self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_ON, data)) + await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_ON, data) - @callback - def _heater_turn_off(self): + async def _async_heater_turn_off(self): """Turn heater toggleable device off.""" data = {ATTR_ENTITY_ID: self.heater_entity_id} - self.hass.async_add_job( - self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data)) + await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data) @property def is_away_mode_on(self): """Return true if away mode is on.""" return self._is_away - def turn_away_mode_on(self): + async def async_turn_away_mode_on(self): """Turn away mode on by setting it on away hold indefinitely.""" self._is_away = True self._saved_target_temp = self._target_temp self._target_temp = self._away_temp - self._async_control_heating() - self.schedule_update_ha_state() + await self._async_control_heating() + await self.async_update_ha_state() - def turn_away_mode_off(self): + async def async_turn_away_mode_off(self): """Turn away off.""" self._is_away = False self._target_temp = self._saved_target_temp - self._async_control_heating() - self.schedule_update_ha_state() + await self._async_control_heating() + await self.async_update_ha_state()