Add support for on/off climate (#25026)

* Add support for on/off climate

* address comments

* Add test for sync overwrite

* Add more tests
pull/25090/head
Pascal Vizeli 2019-07-12 00:28:11 +02:00 committed by Paulus Schoutsen
parent f5a4af40ee
commit d47905d119
5 changed files with 153 additions and 56 deletions

View File

@ -2,13 +2,13 @@
from datetime import timedelta
import functools as ft
import logging
from typing import Any, Awaitable, Dict, List, Optional
from typing import Any, Dict, List, Optional
import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, PRECISION_TENTHS, PRECISION_WHOLE,
STATE_OFF, STATE_ON, TEMP_CELSIUS)
SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
@ -25,7 +25,8 @@ from .const import (
ATTR_HVAC_MODE, ATTR_HVAC_MODES, ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_PRESET_MODES,
ATTR_SWING_MODE, ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODES,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_STEP, DOMAIN, HVAC_MODE_COOL,
HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODES,
SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY,
SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE, SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE,
@ -49,6 +50,9 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__)
TURN_ON_OFF_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
@ -92,6 +96,14 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
component.async_register_entity_service(
SERVICE_TURN_ON, TURN_ON_OFF_SCHEMA,
'async_turn_on'
)
component.async_register_entity_service(
SERVICE_TURN_OFF, TURN_ON_OFF_SCHEMA,
'async_turn_off'
)
component.async_register_entity_service(
SERVICE_SET_HVAC_MODE, SET_HVAC_MODE_SCHEMA,
'async_set_hvac_mode'
@ -338,90 +350,92 @@ class ClimateDevice(Entity):
"""Set new target temperature."""
raise NotImplementedError()
def async_set_temperature(self, **kwargs) -> Awaitable[None]:
"""Set new target temperature.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
await self.hass.async_add_executor_job(
ft.partial(self.set_temperature, **kwargs))
def set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
raise NotImplementedError()
def async_set_humidity(self, humidity: int) -> Awaitable[None]:
"""Set new target humidity.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_humidity, humidity)
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
await self.hass.async_add_executor_job(self.set_humidity, humidity)
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
raise NotImplementedError()
def async_set_fan_mode(self, fan_mode: str) -> Awaitable[None]:
"""Set new target fan mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_fan_mode, fan_mode)
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode)
def set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
raise NotImplementedError()
def async_set_hvac_mode(self, hvac_mode: str) -> Awaitable[None]:
"""Set new target hvac mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_hvac_mode, hvac_mode)
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode)
def set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
raise NotImplementedError()
def async_set_swing_mode(self, swing_mode: str) -> Awaitable[None]:
"""Set new target swing operation.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_swing_mode, swing_mode)
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
await self.hass.async_add_executor_job(self.set_swing_mode, swing_mode)
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
raise NotImplementedError()
def async_set_preset_mode(self, preset_mode: str) -> Awaitable[None]:
"""Set new preset mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_preset_mode, preset_mode)
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
await self.hass.async_add_executor_job(
self.set_preset_mode, preset_mode)
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
raise NotImplementedError()
def async_turn_aux_heat_on(self) -> Awaitable[None]:
"""Turn auxiliary heater on.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_on)
async def async_turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
await self.hass.async_add_executor_job(self.turn_aux_heat_on)
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
raise NotImplementedError()
def async_turn_aux_heat_off(self) -> Awaitable[None]:
"""Turn auxiliary heater off.
async def async_turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
await self.hass.async_add_executor_job(self.turn_aux_heat_off)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.turn_aux_heat_off)
async def async_turn_on(self) -> None:
"""Turn the entity on."""
if hasattr(self, 'turn_on'):
# pylint: disable=no-member
await self.hass.async_add_executor_job(self.turn_on)
return
# Fake turn on
for mode in (HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_COOL):
if mode not in self.hvac_modes:
continue
await self.async_set_hvac_mode(mode)
break
async def async_turn_off(self) -> None:
"""Turn the entity off."""
if hasattr(self, 'turn_off'):
# pylint: disable=no-member
await self.hass.async_add_executor_job(self.turn_off)
return
# Fake turn off
if HVAC_MODE_OFF in self.hvac_modes:
await self.async_set_hvac_mode(HVAC_MODE_OFF)
@property
def supported_features(self) -> int:

View File

@ -114,3 +114,17 @@ nuheat_resume_program:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
turn_on:
description: Turn climate device on.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'
turn_off:
description: Turn climate device off.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.kitchen'

View File

@ -10,7 +10,8 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE,
SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON)
from homeassistant.loader import bind_hass
@ -188,3 +189,25 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
async def async_turn_on(hass, entity_id=None):
"""Turn on device."""
data = {}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, data, blocking=True)
async def async_turn_off(hass, entity_id=None):
"""Turn off device."""
data = {}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, data, blocking=True)

View File

@ -1,9 +1,11 @@
"""The tests for the climate component."""
from unittest.mock import MagicMock
import pytest
import voluptuous as vol
from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA
from homeassistant.components.climate import (
SET_TEMPERATURE_SCHEMA, ClimateDevice)
from tests.common import async_mock_service
@ -37,3 +39,25 @@ async def test_set_temp_schema(hass, caplog):
assert len(calls) == 1
assert calls[-1].data == data
async def test_sync_turn_on(hass):
"""Test if adding turn_on work."""
climate = ClimateDevice()
climate.hass = hass
climate.turn_on = MagicMock()
await climate.async_turn_on()
assert climate.turn_on.called
async def test_sync_turn_off(hass):
"""Test if adding turn_on work."""
climate = ClimateDevice()
climate.hass = hass
climate.turn_off = MagicMock()
await climate.async_turn_off()
assert climate.turn_off.called

View File

@ -4,12 +4,12 @@ import pytest
import voluptuous as vol
from homeassistant.components.climate.const import (
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_HVAC_ACTIONS,
ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODES,
ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE,
ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_ACTIONS, ATTR_HVAC_MODES,
ATTR_MAX_HUMIDITY, ATTR_MAX_TEMP, ATTR_MIN_HUMIDITY, ATTR_MIN_TEMP,
ATTR_PRESET_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, DOMAIN,
HVAC_MODE_COOL, HVAC_MODE_HEAT, PRESET_AWAY, PRESET_ECO)
ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, DOMAIN, HVAC_MODE_COOL,
HVAC_MODE_HEAT, HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO)
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
from homeassistant.util.unit_system import METRIC_SYSTEM
@ -279,3 +279,25 @@ async def test_set_aux_heat_off(hass):
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF
async def test_turn_on(hass):
"""Test turn on device."""
await common.async_set_hvac_mode(hass, HVAC_MODE_OFF, ENTITY_CLIMATE)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_OFF
await common.async_turn_on(hass, ENTITY_CLIMATE)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT
async def test_turn_off(hass):
"""Test turn on device."""
await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT, ENTITY_CLIMATE)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_HEAT
await common.async_turn_off(hass, ENTITY_CLIMATE)
state = hass.states.get(ENTITY_CLIMATE)
assert state.state == HVAC_MODE_OFF