Add support for on/off climate (#25026)
* Add support for on/off climate * address comments * Add test for sync overwrite * Add more testspull/25090/head
parent
f5a4af40ee
commit
d47905d119
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue