Issue #12 - Cannot remove entity_id in configuration

Issue #09 - Hide preset that are not configured
Issue #04 - Add service to update the temperature of a preset
Issue #02 - Limits the usable presets
This commit is contained in:
Jean-Marc Collin
2023-01-14 12:28:26 +01:00
parent c605c5e3ae
commit 10189a59a7
7 changed files with 344 additions and 131 deletions

View File

@@ -94,6 +94,8 @@ from .const import (
PRESET_POWER,
PROPORTIONAL_FUNCTION_TPI,
SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE,
PRESET_AWAY_SUFFIX,
)
from .prop_algorithm import PropAlgorithm
@@ -191,6 +193,16 @@ async def async_setup_entry(
"service_set_presence",
)
platform.async_register_entity_service(
SERVICE_SET_PRESET_TEMPERATURE,
{
vol.Required("preset"): vol.In(CONF_PRESETS),
vol.Optional("temperature"): vol.Coerce(float),
vol.Optional("temperature_away"): vol.Coerce(float),
},
"service_set_preset_temperature",
)
class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Representation of a Versatile Thermostat device."""
@@ -199,6 +211,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_heater_entity_id: str
_prop_algorithm: PropAlgorithm
_async_cancel_cycle: CALLBACK_TYPE
_attr_preset_modes: list[str] | None
def __init__(
self,
@@ -249,6 +262,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._motion_delay_sec = motion_delay_sec
self._motion_preset = motion_preset
self._no_motion_preset = no_motion_preset
self._motion_on = (
self._motion_sensor_entity_id is not None
and self._motion_preset is not None
and self._no_motion_preset is not None
)
self._tpi_coef_int = tpi_coef_int
self._tpi_coef_ext = tpi_coef_ext
self._presence_sensor_entity_id = presence_sensor_entity_id
@@ -266,15 +285,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._saved_hvac_mode = self._hvac_mode
self._support_flags = SUPPORT_FLAGS
if len(presets):
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
self._attr_preset_modes = (
[PRESET_NONE] + list(presets.keys()) + [PRESET_ACTIVITY]
)
_LOGGER.debug("Set preset_modes to %s", self._attr_preset_modes)
else:
_LOGGER.debug("No preset_modes")
self._attr_preset_modes = [PRESET_NONE]
self._presets = presets
self._presets_away = presets_away
@@ -344,6 +355,27 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._overpowering_state = None
self._presence_state = None
# Calculate all possible presets
self._attr_preset_modes = [PRESET_NONE]
if len(presets):
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
for k, v in presets.items():
if v != 0.0:
self._attr_preset_modes.append(k)
# self._attr_preset_modes = (
# [PRESET_NONE] + list(presets.keys()) + [PRESET_ACTIVITY]
# )
_LOGGER.debug(
"After adding presets, preset_modes to %s", self._attr_preset_modes
)
else:
_LOGGER.debug("No preset_modes")
if self._motion_on:
self._attr_preset_modes.append(PRESET_ACTIVITY)
_LOGGER.debug(
"%s - Creation of a new VersatileThermostat entity: unique_id=%s heater_entity_id=%s",
self,
@@ -442,15 +474,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
await self._async_set_preset_mode_internal(preset_mode)
await self._async_control_heating()
async def _async_set_preset_mode_internal(self, preset_mode):
async def _async_set_preset_mode_internal(self, preset_mode, force=False):
"""Set new preset mode."""
_LOGGER.info("%s - Set preset_mode: %s", self, preset_mode)
if preset_mode not in (self._attr_preset_modes or []):
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
if (
preset_mode not in (self._attr_preset_modes or [])
and preset_mode != PRESET_POWER
):
raise ValueError(
f"Got unsupported preset_mode {preset_mode}. Must be one of {self._attr_preset_modes}" # pylint: disable=line-too-long
)
if preset_mode == self._attr_preset_mode:
if preset_mode == self._attr_preset_mode and not force:
# I don't think we need to call async_write_ha_state if we didn't change the state
return
if preset_mode == PRESET_NONE:
@@ -464,7 +499,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if self._attr_preset_mode == PRESET_NONE:
self._saved_target_temp = self._target_temp
self._attr_preset_mode = preset_mode
self._target_temp = self._presets[preset_mode]
preset_temp = self.find_preset_temp(preset_mode)
self._target_temp = (
preset_temp if preset_mode != PRESET_POWER else self._power_temp
)
# Don't saved preset_mode if we are in POWER mode or in Away mode and presence detection is on
if preset_mode != PRESET_POWER:
@@ -472,6 +510,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self.recalculate()
def find_preset_temp(self, preset_mode):
"""Find the right temperature of a preset considering the presence if configured"""
if self._presence_on is False or self._presence_state in [STATE_ON, STATE_HOME]:
return self._presets[preset_mode]
else:
return self._presets_away[self.get_preset_away_name(preset_mode)]
def get_preset_away_name(self, preset_mode):
"""Get the preset name in away mode (when presence is off)"""
return preset_mode + PRESET_AWAY_SUFFIX
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
@@ -1061,6 +1110,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.debug("%s - Updating presence. New state is %s", self, new_state)
self._presence_state = new_state
if self._attr_preset_mode == PRESET_POWER or self._presence_on is False:
_LOGGER.info(
"%s - Ignoring presence change cause in Power preset or presence not configured",
self,
)
return
if new_state is None or new_state not in (
STATE_OFF,
@@ -1082,7 +1135,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
new_temp,
)
else:
new_temp = self._presets_away[self._attr_preset_mode + "_away"]
new_temp = self._presets_away[
self.get_preset_away_name(self._attr_preset_mode)
]
_LOGGER.info(
"%s - No one is at home. Apply temperature %.2f",
self,
@@ -1277,9 +1332,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"eco_temp": self._presets[PRESET_ECO],
"boost_temp": self._presets[PRESET_BOOST],
"comfort_temp": self._presets[PRESET_COMFORT],
"eco_away_temp": self._presets_away[PRESET_ECO + "_away"],
"boost_away_temp": self._presets_away[PRESET_BOOST + "_away"],
"comfort_away_temp": self._presets_away[PRESET_COMFORT + "_away"],
"eco_away_temp": self._presets_away[self.get_preset_away_name(PRESET_ECO)],
"boost_away_temp": self._presets_away[
self.get_preset_away_name(PRESET_BOOST)
],
"comfort_away_temp": self._presets_away[
self.get_preset_away_name(PRESET_COMFORT)
],
"power_temp": self._power_temp,
"on_percent": self._prop_algorithm.on_percent,
"on_time_sec": self._prop_algorithm.on_time_sec,
@@ -1316,11 +1375,45 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
async def service_set_presence(self, presence):
"""Called by a service call:
service: versatile_thermostat.set_presence
entity_id: climate.xxxxxx
data: {
presence: 'on'
}
data:
presence: "off"
target:
entity_id: climate.thermostat_1
"""
_LOGGER.info("%s - Calling service_set_presence, presence: %s", self, presence)
# Do something
self._update_presence(presence)
async def service_set_preset_temperature(
self, preset, temperature=None, temperature_away=None
):
"""Called by a service call:
service: versatile_thermostat.set_preset_temperature
data:
temperature: 17.8
preset: boost
temperature_away: 15
target:
entity_id: climate.thermostat_2
"""
_LOGGER.info(
"%s - Calling service_set_preset_temperature, preset: %s, temperature: %s, temperature_away: %s",
self,
preset,
temperature,
temperature_away,
)
if preset in self._presets:
if temperature is not None:
self._presets[preset] = temperature
if self._presence_on and temperature_away is not None:
self._presets_away[self.get_preset_away_name(preset)] = temperature_away
else:
_LOGGER.warning(
"%s - No preset %s configured for this thermostat. Ignoring set_preset_temperature call",
self,
preset,
)
# If the changed preset is active, change the current temperature
if self._attr_preset_mode == preset:
await self._async_set_preset_mode_internal(preset, force=True)

View File

@@ -2,9 +2,12 @@
from __future__ import annotations
import logging
import copy
import voluptuous as vol
from collections.abc import Mapping
from typing import Any
from homeassistant.core import callback
from homeassistant.config_entries import (
ConfigEntry,
@@ -48,91 +51,46 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HEATER): cv.string,
vol.Required(CONF_TEMP_SENSOR): cv.string,
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): cv.string,
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
]
),
}
)
STEP_TPI_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_TPI_COEF_INT, default=0.6): vol.Coerce(float),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): vol.Coerce(float),
}
)
STEP_PRESETS_DATA_SCHEMA = vol.Schema(
{vol.Optional(v, default=17): vol.Coerce(float) for (k, v) in CONF_PRESETS.items()}
)
STEP_WINDOW_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_WINDOW_SENSOR): cv.string,
vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int,
}
)
STEP_MOTION_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_MOTION_SENSOR): cv.string,
vol.Optional(CONF_MOTION_DELAY, default=30): cv.positive_int,
vol.Optional(CONF_MOTION_PRESET, default="comfort"): vol.In(
CONF_PRESETS_SELECTIONABLE
),
vol.Optional(CONF_NO_MOTION_PRESET, default="eco"): vol.In(
CONF_PRESETS_SELECTIONABLE
),
}
)
STEP_POWER_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_POWER_SENSOR): cv.string,
vol.Optional(CONF_MAX_POWER_SENSOR): cv.string,
vol.Optional(CONF_DEVICE_POWER): vol.Coerce(float),
vol.Optional(CONF_PRESET_POWER): vol.Coerce(float),
}
)
STEP_PRESENCE_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_PRESENCE_SENSOR): cv.string,
}
).extend(
{
vol.Optional(v, default=17): vol.Coerce(float)
for (k, v) in CONF_PRESETS_AWAY.items()
}
)
# Not used but can be useful in other context
# def schema_defaults(schema, **defaults):
# """Create a new schema with default values filled in."""
# copy = schema.extend({})
# for field, field_type in copy.schema.items():
# if isinstance(field_type, vol.In):
# value = None
#
# if value in field_type.container:
# # field.default = vol.default_factory(value)
# field.description = {"suggested_value": value}
# continue
#
# if field.schema in defaults:
# # field.default = vol.default_factory(defaults[field])
# field.description = {"suggested_value": defaults[field]}
# return copy
#
def schema_defaults(schema, **defaults):
"""Create a new schema with default values filled in."""
copy = schema.extend({})
for field, field_type in copy.schema.items():
if isinstance(field_type, vol.In):
value = None
# for dps in dps_list or []:
# if dps.startswith(f"{defaults.get(field)} "):
# value = dps
# break
def add_suggested_values_to_schema(
data_schema: vol.Schema, suggested_values: Mapping[str, Any]
) -> vol.Schema:
"""Make a copy of the schema, populated with suggested values.
if value in field_type.container:
field.default = vol.default_factory(value)
continue
if field.schema in defaults:
field.default = vol.default_factory(defaults[field])
return copy
For each schema marker matching items in `suggested_values`,
the `suggested_value` will be set. The existing `suggested_value` will
be left untouched if there is no matching item.
"""
schema = {}
for key, val in data_schema.schema.items():
new_key = key
if key in suggested_values and isinstance(key, vol.Marker):
# Copy the marker to not modify the flow schema
new_key = copy.copy(key)
new_key.description = {"suggested_value": suggested_values[key]}
schema[new_key] = val
_LOGGER.debug("add_suggested_values_to_schema: schema=%s", schema)
return vol.Schema(schema)
class VersatileThermostatBaseConfigFlow(FlowHandler):
@@ -145,6 +103,76 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
super().__init__()
_LOGGER.debug("CTOR BaseConfigFlow infos: %s", infos)
self._infos = infos
self.STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HEATER): cv.string,
vol.Required(CONF_TEMP_SENSOR): cv.string,
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): cv.string,
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Required(
CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI
): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
]
),
}
)
self.STEP_TPI_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_TPI_COEF_INT, default=0.6): vol.Coerce(float),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): vol.Coerce(float),
}
)
self.STEP_PRESETS_DATA_SCHEMA = vol.Schema(
{
vol.Optional(v, default=0.0): vol.Coerce(float)
for (k, v) in CONF_PRESETS.items()
}
)
self.STEP_WINDOW_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_WINDOW_SENSOR): cv.string,
vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int,
}
)
self.STEP_MOTION_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_MOTION_SENSOR): cv.string,
vol.Optional(CONF_MOTION_DELAY, default=30): cv.positive_int,
vol.Optional(CONF_MOTION_PRESET, default="comfort"): vol.In(
CONF_PRESETS_SELECTIONABLE
),
vol.Optional(CONF_NO_MOTION_PRESET, default="eco"): vol.In(
CONF_PRESETS_SELECTIONABLE
),
}
)
self.STEP_POWER_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_POWER_SENSOR): cv.string,
vol.Optional(CONF_MAX_POWER_SENSOR): cv.string,
vol.Optional(CONF_DEVICE_POWER): vol.Coerce(float),
vol.Optional(CONF_PRESET_POWER): vol.Coerce(float),
}
)
self.STEP_PRESENCE_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_PRESENCE_SENSOR): cv.string,
}
).extend(
{
vol.Optional(v, default=17): vol.Coerce(float)
for (k, v) in CONF_PRESETS_AWAY.items()
}
)
async def validate_input(self, data: dict) -> dict[str]:
"""Validate the user input allows us to connect.
@@ -171,6 +199,21 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
)
raise UnknownEntity(conf)
def merge_user_input(self, data_schema: vol.Schema, user_input: dict):
"""For each schema entry not in user_input, set or remove values in infos"""
self._infos.update(user_input)
for key, _ in data_schema.schema.items():
if key not in user_input and isinstance(key, vol.Marker):
_LOGGER.debug(
"add_empty_values_to_user_input: %s is not in user_input", key
)
if key in self._infos:
self._infos.pop(key)
# else: This don't work but I don't know why. _infos seems broken after this (Not serializable exactly)
# self._infos[key] = user_input[key]
_LOGGER.debug("merge_user_input: infos is now %s", self._infos)
async def generic_step(self, step_id, data_schema, user_input, next_step_function):
"""A generic method step"""
_LOGGER.debug(
@@ -190,11 +233,14 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
self._infos.update(user_input)
self.merge_user_input(data_schema, user_input)
_LOGGER.debug("_info is now: %s", self._infos)
return await next_step_function()
ds = schema_defaults(data_schema, **defaults) # pylint: disable=invalid-name
# ds = schema_defaults(data_schema, **defaults) # pylint: disable=invalid-name
ds = add_suggested_values_to_schema(
data_schema=data_schema, suggested_values=defaults
) # pylint: disable=invalid-name
return self.async_show_form(step_id=step_id, data_schema=ds, errors=errors)
@@ -203,7 +249,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_user user_input=%s", user_input)
return await self.generic_step(
"user", STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
@@ -211,7 +257,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
return await self.generic_step(
"tpi", STEP_TPI_DATA_SCHEMA, user_input, self.async_step_presets
"tpi", self.STEP_TPI_DATA_SCHEMA, user_input, self.async_step_presets
)
async def async_step_presets(self, user_input: dict | None = None) -> FlowResult:
@@ -219,7 +265,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_presets user_input=%s", user_input)
return await self.generic_step(
"presets", STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window
)
async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
@@ -227,7 +273,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_window user_input=%s", user_input)
return await self.generic_step(
"window", STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion
"window", self.STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion
)
async def async_step_motion(self, user_input: dict | None = None) -> FlowResult:
@@ -235,7 +281,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_motion user_input=%s", user_input)
return await self.generic_step(
"motion", STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power
"motion", self.STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power
)
async def async_step_power(self, user_input: dict | None = None) -> FlowResult:
@@ -244,7 +290,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
return await self.generic_step(
"power",
STEP_POWER_DATA_SCHEMA,
self.STEP_POWER_DATA_SCHEMA,
user_input,
self.async_step_presence,
)
@@ -255,7 +301,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
return await self.generic_step(
"presence",
STEP_PRESENCE_DATA_SCHEMA,
self.STEP_PRESENCE_DATA_SCHEMA,
user_input,
self.async_finalize, # pylint: disable=no-member
)
@@ -279,7 +325,7 @@ class VersatileThermostatConfigFlow(
async def async_finalize(self):
"""Finalization of the ConfigEntry creation"""
_LOGGER.debug("CTOR ConfigFlow.async_finalize")
_LOGGER.debug("ConfigFlow.async_finalize")
return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos)
@@ -318,7 +364,7 @@ class VersatileThermostatOptionsFlowHandler(
)
return await self.generic_step(
"user", STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
@@ -328,7 +374,7 @@ class VersatileThermostatOptionsFlowHandler(
)
return await self.generic_step(
"tpi", STEP_TPI_DATA_SCHEMA, user_input, self.async_step_presets
"tpi", self.STEP_TPI_DATA_SCHEMA, user_input, self.async_step_presets
)
async def async_step_presets(self, user_input: dict | None = None) -> FlowResult:
@@ -338,7 +384,7 @@ class VersatileThermostatOptionsFlowHandler(
)
return await self.generic_step(
"presets", STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window
)
async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
@@ -348,7 +394,7 @@ class VersatileThermostatOptionsFlowHandler(
)
return await self.generic_step(
"window", STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion
"window", self.STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion
)
async def async_step_motion(self, user_input: dict | None = None) -> FlowResult:
@@ -358,7 +404,7 @@ class VersatileThermostatOptionsFlowHandler(
)
return await self.generic_step(
"motion", STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power
"motion", self.STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power
)
async def async_step_power(self, user_input: dict | None = None) -> FlowResult:
@@ -369,7 +415,7 @@ class VersatileThermostatOptionsFlowHandler(
return await self.generic_step(
"power",
STEP_POWER_DATA_SCHEMA,
self.STEP_POWER_DATA_SCHEMA,
user_input,
self.async_step_presence, # pylint: disable=no-member
)
@@ -382,7 +428,7 @@ class VersatileThermostatOptionsFlowHandler(
return await self.generic_step(
"presence",
STEP_PRESENCE_DATA_SCHEMA,
self.STEP_PRESENCE_DATA_SCHEMA,
user_input,
self.async_finalize, # pylint: disable=no-member
)

View File

@@ -45,12 +45,14 @@ CONF_PRESETS = {
)
}
PRESET_AWAY_SUFFIX = "_away"
CONF_PRESETS_AWAY = {
p: f"{p}_temp"
for p in (
PRESET_ECO + "_away",
PRESET_BOOST + "_away",
PRESET_COMFORT + "_away",
PRESET_ECO + PRESET_AWAY_SUFFIX,
PRESET_BOOST + PRESET_AWAY_SUFFIX,
PRESET_COMFORT + PRESET_AWAY_SUFFIX,
)
}
@@ -91,3 +93,4 @@ CONF_FUNCTIONS = [
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"

View File

@@ -0,0 +1,71 @@
set_presence:
name: Set presence
description: Force the presence mode in thermostat
target:
entity:
multiple: true
integration: versatile_thermostat
fields:
presence:
name: Presence
description: Presence setting
required: true
advanced: false
example: "on"
default: "on"
selector:
select:
options:
- "on"
- "off"
- "home"
- "not_home"
set_preset_temperature:
name: Set temperature preset
description: Change the target temperature of a preset
target:
entity:
multiple: true
integration: versatile_thermostat
fields:
preset:
name: Preset
description: Preset name
required: true
advanced: false
example: "comfort"
selector:
select:
options:
- "eco"
- "comfort"
- "boost"
temperature:
name: Temperature when present
description: Target temperature for the preset when present
required: false
advanced: false
example: "19.5"
default: "17"
selector:
number:
min: 7
max: 35
step: 0.1
unit_of_measurement: °
mode: slider
temperature_away:
name: Temperature when not present
description: Target temperature for the preset when not present
required: false
advanced: false
example: "17"
default: "15"
selector:
number:
min: 7
max: 35
step: 0.1
unit_of_measurement: °
mode: slider

View File

@@ -25,7 +25,7 @@
},
"presets": {
"title": "Presets",
"description": "For each presets, give the target temperature",
"description": "For each presets, give the target temperature (0 to ignore preset)",
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
@@ -104,7 +104,7 @@
},
"presets": {
"title": "Presets",
"description": "For each presets, give the target temperature",
"description": "For each presets, give the target temperature (0 to ignore preset)",
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",

View File

@@ -25,7 +25,7 @@
},
"presets": {
"title": "Presets",
"description": "For each presets, give the target temperature",
"description": "For each presets, give the target temperature (0 to ignore preset)",
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
@@ -104,7 +104,7 @@
},
"presets": {
"title": "Presets",
"description": "For each presets, give the target temperature",
"description": "For each presets, give the target temperature (0 to ignore preset)",
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",

View File

@@ -25,7 +25,7 @@
},
"presets": {
"title": "Presets",
"description": "Pour chaque preset, donnez la température cible",
"description": "Pour chaque preset, donnez la température cible (0 pour ignorer le preset)",
"data": {
"eco_temp": "Température en preset Eco",
"comfort_temp": "Température en preset Comfort",
@@ -104,7 +104,7 @@
},
"presets": {
"title": "Presets",
"description": "Pour chaque preset, donnez la température cible",
"description": "Pour chaque preset, donnez la température cible (0 pour ignorer le preset)",
"data": {
"eco_temp": "Température en preset Eco",
"comfort_temp": "Température en preset Comfort",