Add service set_preset_mode_with_end_datetime in Netatmo integration (#101674)
* Add Netatmo climate service set_preset_mode_with_end_datetime to allow you to specify an end datetime for a preset mode * Make end date optional * address review comments * Update strings * Update homeassistant/components/netatmo/const.py * Update homeassistant/components/netatmo/strings.json --------- Co-authored-by: Adrien JOLY <adrien.joly-veillith@live.fr> Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
8a4fe5add1
commit
302b444269
5 changed files with 145 additions and 1 deletions
|
@ -8,6 +8,7 @@ from pyatmo.modules import NATherm1
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_PRESET_MODE,
|
||||
DEFAULT_MIN_TEMP,
|
||||
PRESET_AWAY,
|
||||
PRESET_BOOST,
|
||||
|
@ -30,8 +31,10 @@ from homeassistant.helpers import config_validation as cv, entity_platform
|
|||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
ATTR_END_DATETIME,
|
||||
ATTR_HEATING_POWER_REQUEST,
|
||||
ATTR_SCHEDULE_NAME,
|
||||
ATTR_SELECTED_SCHEDULE,
|
||||
|
@ -43,6 +46,7 @@ from .const import (
|
|||
EVENT_TYPE_SET_POINT,
|
||||
EVENT_TYPE_THERM_MODE,
|
||||
NETATMO_CREATE_CLIMATE,
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME,
|
||||
SERVICE_SET_SCHEDULE,
|
||||
)
|
||||
from .data_handler import HOME, SIGNAL_NAME, NetatmoRoom
|
||||
|
@ -59,6 +63,8 @@ SUPPORT_FLAGS = (
|
|||
)
|
||||
SUPPORT_PRESET = [PRESET_AWAY, PRESET_BOOST, PRESET_FROST_GUARD, PRESET_SCHEDULE]
|
||||
|
||||
THERM_MODES = (PRESET_SCHEDULE, PRESET_FROST_GUARD, PRESET_AWAY)
|
||||
|
||||
STATE_NETATMO_SCHEDULE = "schedule"
|
||||
STATE_NETATMO_HG = "hg"
|
||||
STATE_NETATMO_MAX = "max"
|
||||
|
@ -124,6 +130,14 @@ async def async_setup_entry(
|
|||
{vol.Required(ATTR_SCHEDULE_NAME): cv.string},
|
||||
"_async_service_set_schedule",
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME,
|
||||
{
|
||||
vol.Required(ATTR_PRESET_MODE): vol.In(THERM_MODES),
|
||||
vol.Required(ATTR_END_DATETIME): cv.datetime,
|
||||
},
|
||||
"_async_service_set_preset_mode_with_end_datetime",
|
||||
)
|
||||
|
||||
|
||||
class NetatmoThermostat(NetatmoBase, ClimateEntity):
|
||||
|
@ -314,7 +328,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
|
|||
await self._room.async_therm_set(STATE_NETATMO_HOME)
|
||||
elif preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX):
|
||||
await self._room.async_therm_set(PRESET_MAP_NETATMO[preset_mode])
|
||||
elif preset_mode in (PRESET_SCHEDULE, PRESET_FROST_GUARD, PRESET_AWAY):
|
||||
elif preset_mode in THERM_MODES:
|
||||
await self._room.home.async_set_thermmode(PRESET_MAP_NETATMO[preset_mode])
|
||||
else:
|
||||
_LOGGER.error("Preset mode '%s' not available", preset_mode)
|
||||
|
@ -410,6 +424,23 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
|
|||
schedule_id,
|
||||
)
|
||||
|
||||
async def _async_service_set_preset_mode_with_end_datetime(
|
||||
self, **kwargs: Any
|
||||
) -> None:
|
||||
preset_mode = kwargs[ATTR_PRESET_MODE]
|
||||
end_datetime = kwargs[ATTR_END_DATETIME]
|
||||
end_timestamp = int(dt_util.as_timestamp(end_datetime))
|
||||
|
||||
await self._room.home.async_set_thermmode(
|
||||
mode=PRESET_MAP_NETATMO[preset_mode], end_time=end_timestamp
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Setting %s preset to %s with optional end datetime to %s",
|
||||
self._room.home.entity_id,
|
||||
preset_mode,
|
||||
end_timestamp,
|
||||
)
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info for the thermostat."""
|
||||
|
|
|
@ -69,6 +69,7 @@ DEFAULT_PERSON = "unknown"
|
|||
DEFAULT_WEBHOOKS = False
|
||||
|
||||
ATTR_CAMERA_LIGHT_MODE = "camera_light_mode"
|
||||
ATTR_END_DATETIME = "end_datetime"
|
||||
ATTR_EVENT_TYPE = "event_type"
|
||||
ATTR_FACE_URL = "face_url"
|
||||
ATTR_HEATING_POWER_REQUEST = "heating_power_request"
|
||||
|
@ -86,6 +87,7 @@ SERVICE_SET_CAMERA_LIGHT = "set_camera_light"
|
|||
SERVICE_SET_PERSON_AWAY = "set_person_away"
|
||||
SERVICE_SET_PERSONS_HOME = "set_persons_home"
|
||||
SERVICE_SET_SCHEDULE = "set_schedule"
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME = "set_preset_mode_with_end_datetime"
|
||||
|
||||
# Climate events
|
||||
EVENT_TYPE_CANCEL_SET_POINT = "cancel_set_point"
|
||||
|
|
|
@ -26,6 +26,26 @@ set_schedule:
|
|||
selector:
|
||||
text:
|
||||
|
||||
set_preset_mode_with_end_datetime:
|
||||
target:
|
||||
entity:
|
||||
integration: netatmo
|
||||
domain: climate
|
||||
fields:
|
||||
preset_mode:
|
||||
required: true
|
||||
example: "away"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "away"
|
||||
- "Frost Guard"
|
||||
end_datetime:
|
||||
required: true
|
||||
example: '"2019-04-20 05:04:20"'
|
||||
selector:
|
||||
datetime:
|
||||
|
||||
set_persons_home:
|
||||
target:
|
||||
entity:
|
||||
|
|
|
@ -115,6 +115,20 @@
|
|||
"unregister_webhook": {
|
||||
"name": "Unregister webhook",
|
||||
"description": "Unregisters the webhook from the Netatmo backend."
|
||||
},
|
||||
"set_preset_mode_with_end_datetime": {
|
||||
"name": "Set preset mode with end datetime",
|
||||
"description": "Sets the preset mode for a Netatmo climate device. The preset mode must match a preset mode configured at Netatmo.",
|
||||
"fields": {
|
||||
"preset_mode": {
|
||||
"name": "Preset mode",
|
||||
"description": "Climate preset mode such as Schedule, Away or Frost Guard."
|
||||
},
|
||||
"end_datetime": {
|
||||
"name": "End datetime",
|
||||
"description": "Datetime for until when the preset will be active."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
"""The tests for the Netatmo climate platform."""
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from voluptuous.error import MultipleInvalid
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_HVAC_MODE,
|
||||
|
@ -18,11 +20,14 @@ from homeassistant.components.climate import (
|
|||
)
|
||||
from homeassistant.components.netatmo.climate import PRESET_FROST_GUARD, PRESET_SCHEDULE
|
||||
from homeassistant.components.netatmo.const import (
|
||||
ATTR_END_DATETIME,
|
||||
ATTR_SCHEDULE_NAME,
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME,
|
||||
SERVICE_SET_SCHEDULE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .common import selected_platforms, simulate_webhook
|
||||
|
||||
|
@ -458,6 +463,78 @@ async def test_service_schedule_thermostats(
|
|||
assert "summer is not a valid schedule" in caplog.text
|
||||
|
||||
|
||||
async def test_service_preset_mode_with_end_time_thermostats(
|
||||
hass: HomeAssistant, config_entry, caplog: pytest.LogCaptureFixture, netatmo_auth
|
||||
) -> None:
|
||||
"""Test service for set preset mode with end datetime for Netatmo thermostats."""
|
||||
with selected_platforms(["climate"]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
climate_entity_livingroom = "climate.livingroom"
|
||||
|
||||
# Test setting a valid preset mode (that allow an end datetime in Netatmo == THERM_MODES) and a valid end datetime
|
||||
await hass.services.async_call(
|
||||
"netatmo",
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME,
|
||||
{
|
||||
ATTR_ENTITY_ID: climate_entity_livingroom,
|
||||
ATTR_PRESET_MODE: PRESET_AWAY,
|
||||
ATTR_END_DATETIME: (dt_util.now() + timedelta(days=10)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fake webhook thermostat mode change to "Away"
|
||||
response = {
|
||||
"event_type": "therm_mode",
|
||||
"home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "away"},
|
||||
"mode": "away",
|
||||
"previous_mode": "schedule",
|
||||
"push_type": "home_event_changed",
|
||||
}
|
||||
await simulate_webhook(hass, webhook_id, response)
|
||||
|
||||
assert hass.states.get(climate_entity_livingroom).state == "auto"
|
||||
assert (
|
||||
hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away"
|
||||
)
|
||||
|
||||
# Test setting an invalid preset mode (not in THERM_MODES) and a valid end datetime
|
||||
with pytest.raises(MultipleInvalid):
|
||||
await hass.services.async_call(
|
||||
"netatmo",
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME,
|
||||
{
|
||||
ATTR_ENTITY_ID: climate_entity_livingroom,
|
||||
ATTR_PRESET_MODE: PRESET_BOOST,
|
||||
ATTR_END_DATETIME: (dt_util.now() + timedelta(days=10)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test setting a valid preset mode (that allow an end datetime in Netatmo == THERM_MODES) without an end datetime
|
||||
with pytest.raises(MultipleInvalid):
|
||||
await hass.services.async_call(
|
||||
"netatmo",
|
||||
SERVICE_SET_PRESET_MODE_WITH_END_DATETIME,
|
||||
{
|
||||
ATTR_ENTITY_ID: climate_entity_livingroom,
|
||||
ATTR_PRESET_MODE: PRESET_AWAY,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_service_preset_mode_already_boost_valves(
|
||||
hass: HomeAssistant, config_entry, netatmo_auth
|
||||
) -> None:
|
||||
|
|
Loading…
Add table
Reference in a new issue