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:
parent
647a93801c
commit
10a104271e
8 changed files with 79 additions and 49 deletions
|
@ -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()
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue