mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 06:37:52 +00:00
Add toggle service to climate (#100418)
* Add toggle service to climate * Fix mqtt test * Add comments * Fix rebase * Remove not needed properties * Fix toggle service * Fix test * Test * Mod mqtt test --------- Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
cb776593cf
commit
3392660537
@ -14,6 +14,7 @@ from homeassistant.const import (
|
|||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
PRECISION_TENTHS,
|
PRECISION_TENTHS,
|
||||||
PRECISION_WHOLE,
|
PRECISION_WHOLE,
|
||||||
|
SERVICE_TOGGLE,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
@ -166,6 +167,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
"async_turn_off",
|
"async_turn_off",
|
||||||
[ClimateEntityFeature.TURN_OFF],
|
[ClimateEntityFeature.TURN_OFF],
|
||||||
)
|
)
|
||||||
|
component.async_register_entity_service(
|
||||||
|
SERVICE_TOGGLE,
|
||||||
|
{},
|
||||||
|
"async_toggle",
|
||||||
|
[ClimateEntityFeature.TURN_OFF, ClimateEntityFeature.TURN_ON],
|
||||||
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_HVAC_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
{vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
|
{vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
|
||||||
@ -756,7 +763,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if mode not in self.hvac_modes:
|
if mode not in self.hvac_modes:
|
||||||
continue
|
continue
|
||||||
await self.async_set_hvac_mode(mode)
|
await self.async_set_hvac_mode(mode)
|
||||||
break
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def turn_off(self) -> None:
|
def turn_off(self) -> None:
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
@ -772,6 +781,26 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
# Fake turn off
|
# Fake turn off
|
||||||
if HVACMode.OFF in self.hvac_modes:
|
if HVACMode.OFF in self.hvac_modes:
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def toggle(self) -> None:
|
||||||
|
"""Toggle the entity."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def async_toggle(self) -> None:
|
||||||
|
"""Toggle the entity."""
|
||||||
|
# Forward to self.toggle if it's been overridden.
|
||||||
|
if type(self).toggle is not ClimateEntity.toggle:
|
||||||
|
await self.hass.async_add_executor_job(self.toggle)
|
||||||
|
return
|
||||||
|
|
||||||
|
# We assume that since turn_off is supported, HVACMode.OFF is as well.
|
||||||
|
if self.hvac_mode == HVACMode.OFF:
|
||||||
|
await self.async_turn_on()
|
||||||
|
else:
|
||||||
|
await self.async_turn_off()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def supported_features(self) -> ClimateEntityFeature:
|
def supported_features(self) -> ClimateEntityFeature:
|
||||||
|
@ -148,3 +148,11 @@ turn_off:
|
|||||||
domain: climate
|
domain: climate
|
||||||
supported_features:
|
supported_features:
|
||||||
- climate.ClimateEntityFeature.TURN_OFF
|
- climate.ClimateEntityFeature.TURN_OFF
|
||||||
|
|
||||||
|
toggle:
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: climate
|
||||||
|
supported_features:
|
||||||
|
- climate.ClimateEntityFeature.TURN_OFF
|
||||||
|
- climate.ClimateEntityFeature.TURN_ON
|
||||||
|
@ -219,6 +219,10 @@
|
|||||||
"turn_off": {
|
"turn_off": {
|
||||||
"name": "[%key:common::action::turn_off%]",
|
"name": "[%key:common::action::turn_off%]",
|
||||||
"description": "Turns climate device off."
|
"description": "Turns climate device off."
|
||||||
|
},
|
||||||
|
"toggle": {
|
||||||
|
"name": "[%key:common::action::toggle%]",
|
||||||
|
"description": "Toggles climate device, from on to off, or off to on."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -110,12 +110,6 @@ class MockClimateEntity(MockEntity, ClimateEntity):
|
|||||||
"""
|
"""
|
||||||
return [HVACMode.OFF, HVACMode.HEAT]
|
return [HVACMode.OFF, HVACMode.HEAT]
|
||||||
|
|
||||||
def turn_on(self) -> None:
|
|
||||||
"""Turn on."""
|
|
||||||
|
|
||||||
def turn_off(self) -> None:
|
|
||||||
"""Turn off."""
|
|
||||||
|
|
||||||
def set_preset_mode(self, preset_mode: str) -> None:
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set preset mode."""
|
"""Set preset mode."""
|
||||||
self._attr_preset_mode = preset_mode
|
self._attr_preset_mode = preset_mode
|
||||||
@ -129,9 +123,19 @@ class MockClimateEntity(MockEntity, ClimateEntity):
|
|||||||
self._attr_swing_mode = swing_mode
|
self._attr_swing_mode = swing_mode
|
||||||
|
|
||||||
|
|
||||||
|
class MockClimateEntityTestMethods(MockClimateEntity):
|
||||||
|
"""Mock Climate device."""
|
||||||
|
|
||||||
|
def turn_on(self) -> None:
|
||||||
|
"""Turn on."""
|
||||||
|
|
||||||
|
def turn_off(self) -> None:
|
||||||
|
"""Turn off."""
|
||||||
|
|
||||||
|
|
||||||
async def test_sync_turn_on(hass: HomeAssistant) -> None:
|
async def test_sync_turn_on(hass: HomeAssistant) -> None:
|
||||||
"""Test if async turn_on calls sync turn_on."""
|
"""Test if async turn_on calls sync turn_on."""
|
||||||
climate = MockClimateEntity()
|
climate = MockClimateEntityTestMethods()
|
||||||
climate.hass = hass
|
climate.hass = hass
|
||||||
|
|
||||||
climate.turn_on = MagicMock()
|
climate.turn_on = MagicMock()
|
||||||
@ -142,7 +146,7 @@ async def test_sync_turn_on(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
async def test_sync_turn_off(hass: HomeAssistant) -> None:
|
async def test_sync_turn_off(hass: HomeAssistant) -> None:
|
||||||
"""Test if async turn_off calls sync turn_off."""
|
"""Test if async turn_off calls sync turn_off."""
|
||||||
climate = MockClimateEntity()
|
climate = MockClimateEntityTestMethods()
|
||||||
climate.hass = hass
|
climate.hass = hass
|
||||||
|
|
||||||
climate.turn_off = MagicMock()
|
climate.turn_off = MagicMock()
|
||||||
@ -684,3 +688,80 @@ async def test_no_warning_integration_has_migrated(
|
|||||||
" implements HVACMode(s): off, heat and therefore implicitly supports the off, heat methods"
|
" implements HVACMode(s): off, heat and therefore implicitly supports the off, heat methods"
|
||||||
not in caplog.text
|
not in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_turn_on_off_toggle(hass: HomeAssistant) -> None:
|
||||||
|
"""Test turn_on/turn_off/toggle methods."""
|
||||||
|
|
||||||
|
class MockClimateEntityTest(MockClimateEntity):
|
||||||
|
"""Mock Climate device."""
|
||||||
|
|
||||||
|
_attr_hvac_mode = HVACMode.OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self) -> HVACMode:
|
||||||
|
"""Return hvac mode."""
|
||||||
|
return self._attr_hvac_mode
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
|
"""Set new target hvac mode."""
|
||||||
|
self._attr_hvac_mode = hvac_mode
|
||||||
|
|
||||||
|
climate = MockClimateEntityTest()
|
||||||
|
climate.hass = hass
|
||||||
|
|
||||||
|
await climate.async_turn_on()
|
||||||
|
assert climate.hvac_mode == HVACMode.HEAT
|
||||||
|
|
||||||
|
await climate.async_turn_off()
|
||||||
|
assert climate.hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
|
await climate.async_toggle()
|
||||||
|
assert climate.hvac_mode == HVACMode.HEAT
|
||||||
|
await climate.async_toggle()
|
||||||
|
assert climate.hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sync_toggle(hass: HomeAssistant) -> None:
|
||||||
|
"""Test if async toggle calls sync toggle."""
|
||||||
|
|
||||||
|
class MockClimateEntityTest(MockClimateEntity):
|
||||||
|
"""Mock Climate device."""
|
||||||
|
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self) -> HVACMode:
|
||||||
|
"""Return hvac operation ie. heat, cool mode.
|
||||||
|
|
||||||
|
Need to be one of HVACMode.*.
|
||||||
|
"""
|
||||||
|
return HVACMode.HEAT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
|
"""Return the list of available hvac operation modes.
|
||||||
|
|
||||||
|
Need to be a subset of HVAC_MODES.
|
||||||
|
"""
|
||||||
|
return [HVACMode.OFF, HVACMode.HEAT]
|
||||||
|
|
||||||
|
def turn_on(self) -> None:
|
||||||
|
"""Turn on."""
|
||||||
|
|
||||||
|
def turn_off(self) -> None:
|
||||||
|
"""Turn off."""
|
||||||
|
|
||||||
|
def toggle(self) -> None:
|
||||||
|
"""Toggle."""
|
||||||
|
|
||||||
|
climate = MockClimateEntityTest()
|
||||||
|
climate.hass = hass
|
||||||
|
|
||||||
|
climate.toggle = Mock()
|
||||||
|
await climate.async_toggle()
|
||||||
|
|
||||||
|
assert climate.toggle.called
|
||||||
|
@ -504,10 +504,10 @@ async def test_turn_on_and_off_without_power_command(
|
|||||||
assert state.state == "cool"
|
assert state.state == "cool"
|
||||||
mqtt_mock.async_publish.reset_mock()
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
await common.async_turn_off(hass, ENTITY_CLIMATE)
|
|
||||||
state = hass.states.get(ENTITY_CLIMATE)
|
|
||||||
assert climate_off is None or state.state == climate_off
|
|
||||||
if climate_off:
|
if climate_off:
|
||||||
|
await common.async_turn_off(hass, ENTITY_CLIMATE)
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert climate_off is None or state.state == climate_off
|
||||||
assert state.state == "off"
|
assert state.state == "off"
|
||||||
mqtt_mock.async_publish.assert_has_calls([call("mode-topic", "off", 0, False)])
|
mqtt_mock.async_publish.assert_has_calls([call("mode-topic", "off", 0, False)])
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user