mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
parent
91668e99e3
commit
746d1800f9
@ -25,6 +25,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_MODE,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
@ -40,11 +41,10 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_DURATION_DAYS,
|
ATTR_DURATION,
|
||||||
ATTR_DURATION_HOURS,
|
|
||||||
ATTR_DURATION_UNTIL,
|
ATTR_DURATION_UNTIL,
|
||||||
ATTR_SYSTEM_MODE,
|
ATTR_PERIOD,
|
||||||
ATTR_ZONE_TEMP,
|
ATTR_SETPOINT,
|
||||||
CONF_LOCATION_IDX,
|
CONF_LOCATION_IDX,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SCAN_INTERVAL_DEFAULT,
|
SCAN_INTERVAL_DEFAULT,
|
||||||
@ -81,7 +81,7 @@ RESET_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
|
|||||||
SET_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
|
SET_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||||
vol.Required(ATTR_ZONE_TEMP): vol.All(
|
vol.Required(ATTR_SETPOINT): vol.All(
|
||||||
vol.Coerce(float), vol.Range(min=4.0, max=35.0)
|
vol.Coerce(float), vol.Range(min=4.0, max=35.0)
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_DURATION_UNTIL): vol.All(
|
vol.Optional(ATTR_DURATION_UNTIL): vol.All(
|
||||||
@ -222,7 +222,7 @@ def setup_service_functions(
|
|||||||
# Permanent-only modes will use this schema
|
# Permanent-only modes will use this schema
|
||||||
perm_modes = [m[SZ_SYSTEM_MODE] for m in modes if not m[SZ_CAN_BE_TEMPORARY]]
|
perm_modes = [m[SZ_SYSTEM_MODE] for m in modes if not m[SZ_CAN_BE_TEMPORARY]]
|
||||||
if perm_modes: # any of: "Auto", "HeatingOff": permanent only
|
if perm_modes: # any of: "Auto", "HeatingOff": permanent only
|
||||||
schema = vol.Schema({vol.Required(ATTR_SYSTEM_MODE): vol.In(perm_modes)})
|
schema = vol.Schema({vol.Required(ATTR_MODE): vol.In(perm_modes)})
|
||||||
system_mode_schemas.append(schema)
|
system_mode_schemas.append(schema)
|
||||||
|
|
||||||
modes = [m for m in modes if m[SZ_CAN_BE_TEMPORARY]]
|
modes = [m for m in modes if m[SZ_CAN_BE_TEMPORARY]]
|
||||||
@ -232,8 +232,8 @@ def setup_service_functions(
|
|||||||
if temp_modes: # any of: "AutoWithEco", permanent or for 0-24 hours
|
if temp_modes: # any of: "AutoWithEco", permanent or for 0-24 hours
|
||||||
schema = vol.Schema(
|
schema = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_SYSTEM_MODE): vol.In(temp_modes),
|
vol.Required(ATTR_MODE): vol.In(temp_modes),
|
||||||
vol.Optional(ATTR_DURATION_HOURS): vol.All(
|
vol.Optional(ATTR_DURATION): vol.All(
|
||||||
cv.time_period,
|
cv.time_period,
|
||||||
vol.Range(min=timedelta(hours=0), max=timedelta(hours=24)),
|
vol.Range(min=timedelta(hours=0), max=timedelta(hours=24)),
|
||||||
),
|
),
|
||||||
@ -246,8 +246,8 @@ def setup_service_functions(
|
|||||||
if temp_modes: # any of: "Away", "Custom", "DayOff", permanent or for 1-99 days
|
if temp_modes: # any of: "Away", "Custom", "DayOff", permanent or for 1-99 days
|
||||||
schema = vol.Schema(
|
schema = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_SYSTEM_MODE): vol.In(temp_modes),
|
vol.Required(ATTR_MODE): vol.In(temp_modes),
|
||||||
vol.Optional(ATTR_DURATION_DAYS): vol.All(
|
vol.Optional(ATTR_PERIOD): vol.All(
|
||||||
cv.time_period,
|
cv.time_period,
|
||||||
vol.Range(min=timedelta(days=1), max=timedelta(days=99)),
|
vol.Range(min=timedelta(days=1), max=timedelta(days=99)),
|
||||||
),
|
),
|
||||||
|
@ -29,7 +29,7 @@ from homeassistant.components.climate import (
|
|||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.const import PRECISION_TENTHS, UnitOfTemperature
|
from homeassistant.const import ATTR_MODE, PRECISION_TENTHS, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -38,11 +38,10 @@ from homeassistant.util import dt as dt_util
|
|||||||
|
|
||||||
from . import EVOHOME_KEY
|
from . import EVOHOME_KEY
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_DURATION_DAYS,
|
ATTR_DURATION,
|
||||||
ATTR_DURATION_HOURS,
|
|
||||||
ATTR_DURATION_UNTIL,
|
ATTR_DURATION_UNTIL,
|
||||||
ATTR_SYSTEM_MODE,
|
ATTR_PERIOD,
|
||||||
ATTR_ZONE_TEMP,
|
ATTR_SETPOINT,
|
||||||
EvoService,
|
EvoService,
|
||||||
)
|
)
|
||||||
from .coordinator import EvoDataUpdateCoordinator
|
from .coordinator import EvoDataUpdateCoordinator
|
||||||
@ -180,7 +179,7 @@ class EvoZone(EvoChild, EvoClimateEntity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# otherwise it is EvoService.SET_ZONE_OVERRIDE
|
# otherwise it is EvoService.SET_ZONE_OVERRIDE
|
||||||
temperature = max(min(data[ATTR_ZONE_TEMP], self.max_temp), self.min_temp)
|
temperature = max(min(data[ATTR_SETPOINT], self.max_temp), self.min_temp)
|
||||||
|
|
||||||
if ATTR_DURATION_UNTIL in data:
|
if ATTR_DURATION_UNTIL in data:
|
||||||
duration: timedelta = data[ATTR_DURATION_UNTIL]
|
duration: timedelta = data[ATTR_DURATION_UNTIL]
|
||||||
@ -349,16 +348,16 @@ class EvoController(EvoClimateEntity):
|
|||||||
Data validation is not required, it will have been done upstream.
|
Data validation is not required, it will have been done upstream.
|
||||||
"""
|
"""
|
||||||
if service == EvoService.SET_SYSTEM_MODE:
|
if service == EvoService.SET_SYSTEM_MODE:
|
||||||
mode = data[ATTR_SYSTEM_MODE]
|
mode = data[ATTR_MODE]
|
||||||
else: # otherwise it is EvoService.RESET_SYSTEM
|
else: # otherwise it is EvoService.RESET_SYSTEM
|
||||||
mode = EvoSystemMode.AUTO_WITH_RESET
|
mode = EvoSystemMode.AUTO_WITH_RESET
|
||||||
|
|
||||||
if ATTR_DURATION_DAYS in data:
|
if ATTR_PERIOD in data:
|
||||||
until = dt_util.start_of_local_day()
|
until = dt_util.start_of_local_day()
|
||||||
until += data[ATTR_DURATION_DAYS]
|
until += data[ATTR_PERIOD]
|
||||||
|
|
||||||
elif ATTR_DURATION_HOURS in data:
|
elif ATTR_DURATION in data:
|
||||||
until = dt_util.now() + data[ATTR_DURATION_HOURS]
|
until = dt_util.now() + data[ATTR_DURATION]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
until = None
|
until = None
|
||||||
|
@ -18,11 +18,10 @@ USER_DATA: Final = "user_data"
|
|||||||
SCAN_INTERVAL_DEFAULT: Final = timedelta(seconds=300)
|
SCAN_INTERVAL_DEFAULT: Final = timedelta(seconds=300)
|
||||||
SCAN_INTERVAL_MINIMUM: Final = timedelta(seconds=60)
|
SCAN_INTERVAL_MINIMUM: Final = timedelta(seconds=60)
|
||||||
|
|
||||||
ATTR_SYSTEM_MODE: Final = "mode"
|
ATTR_PERIOD: Final = "period" # number of days
|
||||||
ATTR_DURATION_DAYS: Final = "period"
|
ATTR_DURATION: Final = "duration" # number of minutes, <24h
|
||||||
ATTR_DURATION_HOURS: Final = "duration"
|
|
||||||
|
|
||||||
ATTR_ZONE_TEMP: Final = "setpoint"
|
ATTR_SETPOINT: Final = "setpoint"
|
||||||
ATTR_DURATION_UNTIL: Final = "duration"
|
ATTR_DURATION_UNTIL: Final = "duration"
|
||||||
|
|
||||||
|
|
||||||
|
177
tests/components/evohome/test_evo_services.py
Normal file
177
tests/components/evohome/test_evo_services.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
"""The tests for the native services of Evohome."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from evohomeasync2 import EvohomeClient
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.evohome.const import (
|
||||||
|
ATTR_DURATION,
|
||||||
|
ATTR_PERIOD,
|
||||||
|
ATTR_SETPOINT,
|
||||||
|
DOMAIN,
|
||||||
|
EvoService,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("install", ["default"])
|
||||||
|
async def test_service_refresh_system(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
evohome: EvohomeClient,
|
||||||
|
) -> None:
|
||||||
|
"""Test Evohome's refresh_system service (for all temperature control systems)."""
|
||||||
|
|
||||||
|
# EvoService.REFRESH_SYSTEM
|
||||||
|
with patch("evohomeasync2.location.Location.update") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.REFRESH_SYSTEM,
|
||||||
|
{},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("install", ["default"])
|
||||||
|
async def test_service_reset_system(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
ctl_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Evohome's reset_system service (for a temperature control system)."""
|
||||||
|
|
||||||
|
# EvoService.RESET_SYSTEM (if SZ_AUTO_WITH_RESET in modes)
|
||||||
|
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.RESET_SYSTEM,
|
||||||
|
{},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with("AutoWithReset", until=None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("install", ["default"])
|
||||||
|
async def test_ctl_set_system_mode(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
ctl_id: str,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test Evohome's set_system_mode service (for a temperature control system)."""
|
||||||
|
|
||||||
|
# EvoService.SET_SYSTEM_MODE: Auto
|
||||||
|
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.SET_SYSTEM_MODE,
|
||||||
|
{
|
||||||
|
ATTR_MODE: "Auto",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with("Auto", until=None)
|
||||||
|
|
||||||
|
freezer.move_to("2024-07-10T12:00:00+00:00")
|
||||||
|
|
||||||
|
# EvoService.SET_SYSTEM_MODE: AutoWithEco, hours=12
|
||||||
|
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.SET_SYSTEM_MODE,
|
||||||
|
{
|
||||||
|
ATTR_MODE: "AutoWithEco",
|
||||||
|
ATTR_DURATION: {"hours": 12},
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with(
|
||||||
|
"AutoWithEco", until=datetime(2024, 7, 11, 0, 0, tzinfo=UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
# EvoService.SET_SYSTEM_MODE: Away, days=7
|
||||||
|
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.SET_SYSTEM_MODE,
|
||||||
|
{
|
||||||
|
ATTR_MODE: "Away",
|
||||||
|
ATTR_PERIOD: {"days": 7},
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with(
|
||||||
|
"Away", until=datetime(2024, 7, 16, 23, 0, tzinfo=UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("install", ["default"])
|
||||||
|
async def test_zone_clear_zone_override(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
zone_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Evohome's clear_zone_override service (for a heating zone)."""
|
||||||
|
|
||||||
|
# EvoZoneMode.FOLLOW_SCHEDULE
|
||||||
|
with patch("evohomeasync2.zone.Zone.reset") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.RESET_ZONE_OVERRIDE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: zone_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("install", ["default"])
|
||||||
|
async def test_zone_set_zone_override(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
zone_id: str,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test Evohome's set_zone_override service (for a heating zone)."""
|
||||||
|
|
||||||
|
freezer.move_to("2024-07-10T12:00:00+00:00")
|
||||||
|
|
||||||
|
# EvoZoneMode.PERMANENT_OVERRIDE
|
||||||
|
with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.SET_ZONE_OVERRIDE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: zone_id,
|
||||||
|
ATTR_SETPOINT: 19.5,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with(19.5, until=None)
|
||||||
|
|
||||||
|
# EvoZoneMode.TEMPORARY_OVERRIDE
|
||||||
|
with patch("evohomeasync2.zone.Zone.set_temperature") as mock_fcn:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
EvoService.SET_ZONE_OVERRIDE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: zone_id,
|
||||||
|
ATTR_SETPOINT: 19.5,
|
||||||
|
ATTR_DURATION: {"minutes": 135},
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_fcn.assert_awaited_once_with(
|
||||||
|
19.5, until=datetime(2024, 7, 10, 14, 15, tzinfo=UTC)
|
||||||
|
)
|
@ -1,4 +1,4 @@
|
|||||||
"""The tests for evohome."""
|
"""The tests for Evohome."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ from evohomeasync2 import EvohomeClient, exceptions as exc
|
|||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.evohome.const import DOMAIN, EvoService
|
from homeassistant.components.evohome.const import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
@ -187,41 +187,3 @@ async def test_setup(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
assert hass.services.async_services_for_domain(DOMAIN).keys() == snapshot
|
assert hass.services.async_services_for_domain(DOMAIN).keys() == snapshot
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("install", ["default"])
|
|
||||||
async def test_service_refresh_system(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
evohome: EvohomeClient,
|
|
||||||
) -> None:
|
|
||||||
"""Test EvoService.REFRESH_SYSTEM of an evohome system."""
|
|
||||||
|
|
||||||
# EvoService.REFRESH_SYSTEM
|
|
||||||
with patch("evohomeasync2.location.Location.update") as mock_fcn:
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
EvoService.REFRESH_SYSTEM,
|
|
||||||
{},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mock_fcn.assert_awaited_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("install", ["default"])
|
|
||||||
async def test_service_reset_system(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
evohome: EvohomeClient,
|
|
||||||
) -> None:
|
|
||||||
"""Test EvoService.RESET_SYSTEM of an evohome system."""
|
|
||||||
|
|
||||||
# EvoService.RESET_SYSTEM (if SZ_AUTO_WITH_RESET in modes)
|
|
||||||
with patch("evohomeasync2.control_system.ControlSystem.set_mode") as mock_fcn:
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
EvoService.RESET_SYSTEM,
|
|
||||||
{},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
mock_fcn.assert_awaited_once_with("AutoWithReset", until=None)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user