Cleanup climate platform for async update_ha_state / migrate generic thermostat (#5679)

* Cleanup climate from blocking stuff / migrate generic

* Migrate generic thermostat

* fix tests

* fix lint
This commit is contained in:
Pascal Vizeli 2017-02-02 06:44:05 +01:00 committed by Paulus Schoutsen
parent 647a93801c
commit 10a104271e
8 changed files with 79 additions and 49 deletions

View file

@ -135,27 +135,27 @@ class DemoClimate(ClimateDevice):
kwargs.get(ATTR_TARGET_TEMP_LOW) is not None:
self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
self.update_ha_state()
self.schedule_update_ha_state()
def set_humidity(self, humidity):
"""Set new target temperature."""
self._target_humidity = humidity
self.update_ha_state()
self.schedule_update_ha_state()
def set_swing_mode(self, swing_mode):
"""Set new target temperature."""
self._current_swing_mode = swing_mode
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
self._current_fan_mode = fan
self.update_ha_state()
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
self._current_operation = operation_mode
self.update_ha_state()
self.schedule_update_ha_state()
@property
def current_swing_mode(self):
@ -170,24 +170,24 @@ class DemoClimate(ClimateDevice):
def turn_away_mode_on(self):
"""Turn away mode on."""
self._away = True
self.update_ha_state()
self.schedule_update_ha_state()
def turn_away_mode_off(self):
"""Turn away mode off."""
self._away = False
self.update_ha_state()
self.schedule_update_ha_state()
def set_hold_mode(self, hold):
"""Update hold mode on."""
self._hold = hold
self.update_ha_state()
self.schedule_update_ha_state()
def turn_aux_heat_on(self):
"""Turn away auxillary heater on."""
self._aux = True
self.update_ha_state()
self.schedule_update_ha_state()
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
self._aux = False
self.update_ha_state()
self.schedule_update_ha_state()

View file

@ -69,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for thermostat in target_thermostats:
thermostat.set_fan_min_on_time(str(fan_min_on_time))
thermostat.update_ha_state(True)
thermostat.schedule_update_ha_state(True)
def resume_program_set_service(service):
"""Resume the program on the target thermostats."""
@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for thermostat in target_thermostats:
thermostat.resume_program(resume_all)
thermostat.update_ha_state(True)
thermostat.schedule_update_ha_state(True)
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))

View file

@ -4,17 +4,19 @@ Adds support for generic thermostat units.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.generic_thermostat/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import switch
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -48,7 +50,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the generic thermostat."""
name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
@ -60,7 +63,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
add_devices([GenericThermostat(
yield from async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, tolerance)])
@ -86,12 +89,14 @@ class GenericThermostat(ClimateDevice):
self._target_temp = target_temp
self._unit = hass.config.units.temperature_unit
track_state_change(hass, sensor_entity_id, self._sensor_changed)
track_state_change(hass, heater_entity_id, self._switch_changed)
async_track_state_change(
hass, sensor_entity_id, self._async_sensor_changed)
async_track_state_change(
hass, heater_entity_id, self._async_switch_changed)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state:
self._update_temp(sensor_state)
self._async_update_temp(sensor_state)
@property
def should_poll(self):
@ -128,14 +133,15 @@ class GenericThermostat(ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temp
def set_temperature(self, **kwargs):
@asyncio.coroutine
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._control_heating()
self.schedule_update_ha_state()
self._async_control_heating()
yield from self.async_update_ha_state()
@property
def min_temp(self):
@ -157,22 +163,25 @@ class GenericThermostat(ClimateDevice):
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
def _sensor_changed(self, entity_id, old_state, new_state):
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):
"""Called when temperature changes."""
if new_state is None:
return
self._update_temp(new_state)
self._control_heating()
self.schedule_update_ha_state()
self._async_update_temp(new_state)
self._async_control_heating()
yield from self.async_update_ha_state()
def _switch_changed(self, entity_id, old_state, new_state):
@callback
def _async_switch_changed(self, entity_id, old_state, new_state):
"""Called when heater switch changes state."""
if new_state is None:
return
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def _update_temp(self, state):
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
@ -182,7 +191,8 @@ class GenericThermostat(ClimateDevice):
except ValueError as ex:
_LOGGER.error('Unable to update from sensor: %s', ex)
def _control_heating(self):
@callback
def _async_control_heating(self):
"""Check if we need to turn heating on or off."""
if not self._active and None not in (self._cur_temp,
self._target_temp):
@ -198,9 +208,9 @@ class GenericThermostat(ClimateDevice):
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)
long_enough = condition.state(
self.hass, self.heater_entity_id, current_state,
self.min_cycle_duration)
if not long_enough:
return
@ -210,12 +220,12 @@ class GenericThermostat(ClimateDevice):
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_hot = self._cur_temp - self._target_temp > self._tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
else:
is_heating = self._is_device_active
if is_heating:
@ -223,12 +233,12 @@ class GenericThermostat(ClimateDevice):
if too_hot:
_LOGGER.info('Turning off heater %s',
self.heater_entity_id)
switch.turn_off(self.hass, self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > self._tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.turn_on(self.hass, self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
@property
def _is_device_active(self):

View file

@ -135,7 +135,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[value_type] = value
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
@ -145,7 +145,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.update_ha_state()
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
@ -156,7 +156,7 @@ class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
self.update_ha_state()
self.schedule_update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""

View file

@ -111,7 +111,6 @@ class NetatmoThermostat(ClimateDevice):
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = True
self.update_ha_state()
def turn_away_mode_off(self):
"""Turn away off."""
@ -119,7 +118,6 @@ class NetatmoThermostat(ClimateDevice):
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
self.update_ha_state()
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
"""Set new target temperature for 2 hours."""
@ -131,7 +129,6 @@ class NetatmoThermostat(ClimateDevice):
mode, temperature, endTimeOffset)
self._target_temperature = temperature
self._away = False
self.update_ha_state()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):

View file

@ -223,10 +223,10 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
# ZXT-120 responds only to whole int
value.data = round(temperature, 0)
self._target_temperature = temperature
self.update_ha_state()
self.schedule_update_ha_state()
else:
value.data = temperature
self.update_ha_state()
self.schedule_update_ha_state()
break
def set_fan_mode(self, fan):

View file

@ -11,6 +11,7 @@ import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
@ -20,6 +21,7 @@ from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
ATTR_ENTITY_ID)
from homeassistant.components import group
from homeassistant.util.async import run_callback_threadsafe
DOMAIN = 'switch'
SCAN_INTERVAL = timedelta(seconds=30)
@ -47,21 +49,39 @@ _LOGGER = logging.getLogger(__name__)
def is_on(hass, entity_id=None):
"""Return if the switch is on based on the statemachine."""
"""Return if the switch is on based on the statemachine.
Async friendly.
"""
entity_id = entity_id or ENTITY_ID_ALL_SWITCHES
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id=None):
"""Turn all or specified switch on."""
run_callback_threadsafe(
hass.loop, async_turn_on, hass, entity_id).result()
@callback
def async_turn_on(hass, entity_id=None):
"""Turn all or specified switch on."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
def turn_off(hass, entity_id=None):
"""Turn all or specified switch off."""
run_callback_threadsafe(
hass.loop, async_turn_off, hass, entity_id).result()
@callback
def async_turn_off(hass, entity_id=None):
"""Turn all or specified switch off."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
hass.async_add_job(
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
def toggle(hass, entity_id=None):

View file

@ -199,7 +199,10 @@ numeric_state_from_config = _threaded_factory(async_numeric_state_from_config)
def state(hass, entity, req_state, for_period=None):
"""Test if state matches requirements."""
"""Test if state matches requirements.
Async friendly.
"""
if isinstance(entity, str):
entity = hass.states.get(entity)
@ -357,7 +360,7 @@ def time_from_config(config, config_validation=True):
def zone(hass, zone_ent, entity):
"""Test if zone-condition matches.
Can be run async.
Async friendly.
"""
if isinstance(zone_ent, str):
zone_ent = hass.states.get(zone_ent)