Compare commits

...

2 Commits

Author SHA1 Message Date
Jean-Marc Collin 10189a59a7 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
2023-01-14 12:28:26 +01:00
Jean-Marc Collin c605c5e3ae issue #11 - Rename coeff_c and coeff_t to coeff_int et coeff_ext
issue #10 - Add a service to force the presence or non presence
issue #07 - Make external temperature sensor mandatory for TPI mode
issue #06 - Use person.xxx as presence detector directly
issue #02 - Limits the usable presets
issue #01 - Add a temperature configuration for present / not present for each preset
2023-01-11 23:11:04 +01:00
8 changed files with 538 additions and 435 deletions
+215 -125
View File
@@ -4,6 +4,8 @@ import logging
from datetime import timedelta, datetime
from typing import Any, Mapping
import voluptuous as vol
from homeassistant.core import (
HomeAssistant,
callback,
@@ -23,7 +25,7 @@ from homeassistant.helpers.event import (
)
from homeassistant.exceptions import ConditionError
from homeassistant.helpers import condition
from homeassistant.helpers import condition, entity_platform, config_validation as cv
from homeassistant.components.climate.const import (
ATTR_PRESET_MODE,
@@ -62,6 +64,8 @@ from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_HOME,
STATE_NOT_HOME,
)
from .const import (
@@ -79,17 +83,19 @@ from .const import (
CONF_NO_MOTION_PRESET,
CONF_DEVICE_POWER,
CONF_PRESETS,
CONF_PRESETS_AWAY,
CONF_CYCLE_MIN,
CONF_PROP_FUNCTION,
CONF_PROP_BIAS,
CONF_TPI_COEF_C,
CONF_TPI_COEF_T,
CONF_TPI_COEF_INT,
CONF_TPI_COEF_EXT,
CONF_PRESENCE_SENSOR,
CONF_NO_PRESENCE_PRESET,
CONF_NO_PRESENCE_TEMP_OFFSET,
CONF_PRESET_POWER,
SUPPORT_FLAGS,
PRESET_POWER,
PROPORTIONAL_FUNCTION_TPI,
SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE,
PRESET_AWAY_SUFFIX,
)
from .prop_algorithm import PropAlgorithm
@@ -112,7 +118,6 @@ async def async_setup_entry(
heater_entity_id = entry.data.get(CONF_HEATER)
cycle_min = entry.data.get(CONF_CYCLE_MIN)
proportional_function = entry.data.get(CONF_PROP_FUNCTION)
proportional_bias = entry.data.get(CONF_PROP_BIAS)
temp_sensor_entity_id = entry.data.get(CONF_TEMP_SENSOR)
ext_temp_sensor_entity_id = entry.data.get(CONF_EXTERNAL_TEMP_SENSOR)
power_sensor_entity_id = entry.data.get(CONF_POWER_SENSOR)
@@ -124,11 +129,10 @@ async def async_setup_entry(
motion_preset = entry.data.get(CONF_MOTION_PRESET)
no_motion_preset = entry.data.get(CONF_NO_MOTION_PRESET)
device_power = entry.data.get(CONF_DEVICE_POWER)
tpi_coefc = entry.data.get(CONF_TPI_COEF_C)
tpi_coeft = entry.data.get(CONF_TPI_COEF_T)
tpi_coef_int = entry.data.get(CONF_TPI_COEF_INT)
tpi_coef_ext = entry.data.get(CONF_TPI_COEF_EXT)
presence_sensor_entity_id = entry.data.get(CONF_PRESENCE_SENSOR)
no_presence_preset = entry.data.get(CONF_NO_PRESENCE_PRESET)
no_presence_offset = entry.data.get(CONF_NO_PRESENCE_TEMP_OFFSET)
power_temp = entry.data.get(CONF_PRESET_POWER)
presets = {}
for (key, value) in CONF_PRESETS.items():
@@ -138,6 +142,14 @@ async def async_setup_entry(
else:
_LOGGER.debug("value %s not found in Entry", value)
presets_away = {}
for (key, value) in CONF_PRESETS_AWAY.items():
_LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry.data:
presets_away[key] = entry.data.get(value)
else:
_LOGGER.debug("value %s not found in Entry", value)
async_add_entities(
[
VersatileThermostat(
@@ -147,7 +159,6 @@ async def async_setup_entry(
heater_entity_id,
cycle_min,
proportional_function,
proportional_bias,
temp_sensor_entity_id,
ext_temp_sensor_entity_id,
power_sensor_entity_id,
@@ -159,17 +170,39 @@ async def async_setup_entry(
motion_preset,
no_motion_preset,
presets,
presets_away,
device_power,
tpi_coefc,
tpi_coeft,
tpi_coef_int,
tpi_coef_ext,
presence_sensor_entity_id,
no_presence_preset,
no_presence_offset,
power_temp,
)
],
True,
)
# Add services
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_SET_PRESENCE,
{
vol.Required("presence"): vol.In(
[STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME]
),
},
"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."""
@@ -178,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,
@@ -187,7 +221,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
heater_entity_id,
cycle_min,
proportional_function,
proportional_bias,
temp_sensor_entity_id,
ext_temp_sensor_entity_id,
power_sensor_entity_id,
@@ -199,12 +232,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
motion_preset,
no_motion_preset,
presets,
presets_away,
device_power,
tpi_coefc,
tpi_coeft,
tpi_coef_int,
tpi_coef_ext,
presence_sensor_entity_id,
no_presence_preset,
no_presence_offset,
power_temp,
) -> None:
"""Initialize the thermostat."""
@@ -218,7 +251,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._heater_entity_id = heater_entity_id
self._cycle_min = cycle_min
self._proportional_function = proportional_function
self._proportional_bias = proportional_bias
self._temp_sensor_entity_id = temp_sensor_entity_id
self._ext_temp_sensor_entity_id = ext_temp_sensor_entity_id
self._power_sensor_entity_id = power_sensor_entity_id
@@ -230,22 +262,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._motion_delay_sec = motion_delay_sec
self._motion_preset = motion_preset
self._no_motion_preset = no_motion_preset
self._tpi_coefc = tpi_coefc
self._tpi_coeft = tpi_coeft
self._presence_sensor_entity_id = presence_sensor_entity_id
self._no_presence_preset = no_presence_preset
self._no_presence_offset = no_presence_offset
self._presence_on = self._presence_sensor_entity_id and (
self._no_presence_preset is not None or self._no_presence_offset is not None
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
)
if self._presence_on:
if self._no_presence_preset is not None:
self._no_presence_offset = 0
else:
self._no_presence_preset = None
self._no_presence_offset = 0
_LOGGER.info("%s - Presence management is not fully configured.", self)
self._tpi_coef_int = tpi_coef_int
self._tpi_coef_ext = tpi_coef_ext
self._presence_sensor_entity_id = presence_sensor_entity_id
self._power_temp = power_temp
self._presence_on = self._presence_sensor_entity_id != None
# TODO if self.ac_mode:
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
@@ -257,17 +285,16 @@ 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
_LOGGER.debug("%s - presets are set to: %s", self, self._presets)
self._presets_away = presets_away
_LOGGER.debug(
"%s - presets are set to: %s, away: %s",
self,
self._presets,
self._presets_away,
)
# Will be restored if possible
self._attr_preset_mode = None
self._saved_preset_mode = None
@@ -306,14 +333,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.warning(
"Using TPI function but not external temperature sensor is set. Removing the delta temp ext factor. Thermostat will not be fully operationnal" # pylint: disable=line-too-long
)
self._tpi_coeft = 0
self._tpi_coef_ext = 0
# Initiate the ProportionalAlgorithm
self._prop_algorithm = PropAlgorithm(
self._proportional_function,
self._proportional_bias,
self._tpi_coefc,
self._tpi_coeft,
self._tpi_coef_int,
self._tpi_coef_ext,
self._cycle_min,
)
@@ -329,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,
@@ -427,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:
@@ -449,16 +499,28 @@ 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 and (
not self._presence_on or preset_mode != self._no_presence_preset
):
if preset_mode != PRESET_POWER:
self._saved_preset_mode = self._attr_preset_mode
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)
@@ -1048,65 +1110,48 @@ 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, STATE_ON):
if new_state is None or new_state not in (
STATE_OFF,
STATE_ON,
STATE_HOME,
STATE_NOT_HOME,
):
return
if self._attr_preset_mode not in [PRESET_BOOST, PRESET_COMFORT, PRESET_ECO]:
return
# Change temperature or preset
if self._no_presence_preset:
_LOGGER.debug("%s - presence change in preset mode", self)
new_preset = None
no_presence_preset = self._no_presence_preset
if new_state == STATE_OFF:
new_preset = no_presence_preset
self._saved_preset_mode = self._attr_preset_mode
_LOGGER.info(
"%s - No one is at home. Set to preset %s (saved_preset is %s)",
self,
new_preset,
self._saved_preset_mode,
)
elif self._attr_preset_mode == no_presence_preset:
new_preset = self._saved_preset_mode
_LOGGER.info(
"%s - Someone is back home. Restoring preset to %s",
self,
new_preset,
)
else:
_LOGGER.debug(
"%s - presence change ignored (not in %s preset or not ON)",
self,
no_presence_preset,
)
if new_preset:
self.hass.create_task(self.async_set_preset_mode(new_preset))
# Change temperature with preset named _way
new_temp = None
if new_state == STATE_ON or new_state == STATE_HOME:
new_temp = self._presets[self._attr_preset_mode]
_LOGGER.info(
"%s - Someone is back home. Restoring temperature to %.2f",
self,
new_temp,
)
else:
new_temp = None
if new_state == STATE_OFF:
self._saved_target_temp = self._target_temp
_LOGGER.info(
"%s - No one is at home. Apply offset to temperature %.2f (saved_target_temp is %.2f)",
self,
self._no_presence_offset,
self._saved_target_temp,
)
new_temp = self._target_temp + self._no_presence_offset
else:
new_temp = self._saved_target_temp
_LOGGER.info(
"%s - Someone is back home. Restoring temperature to %.2f",
self,
self._saved_target_temp,
)
if new_temp is not None:
_LOGGER.debug(
"%s - presence change in temperature mode new_temp will be: %.2f",
self,
new_temp,
)
self._target_temp = new_temp
self.recalculate()
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,
new_temp,
)
if new_temp is not None:
_LOGGER.debug(
"%s - presence change in temperature mode new_temp will be: %.2f",
self,
new_temp,
)
self._target_temp = new_temp
self.recalculate()
def _update_motion_temp(self):
"""Update the temperature considering the ACTIVITY preset and current motion state"""
@@ -1284,11 +1329,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Update the custom extra attributes for the entity"""
self._attr_extra_state_attributes = {
"away_temp": self._presets[PRESET_AWAY],
"eco_temp": self._presets[PRESET_ECO],
"boost_temp": self._presets[PRESET_BOOST],
"comfort_temp": self._presets[PRESET_COMFORT],
"power_temp": self._presets[PRESET_POWER],
"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,
"off_time_sec": self._prop_algorithm.off_time_sec,
@@ -1296,18 +1347,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"current_power": self._current_power,
"current_power_max": self._current_power_max,
"cycle_min": self._cycle_min,
"bias": self._proportional_bias,
"function": self._proportional_function,
"tpi_coefc": self._tpi_coefc,
"tpi_coeft": self._tpi_coeft,
"tpi_coef_int": self._tpi_coef_int,
"tpi_coef_ext": self._tpi_coef_ext,
"saved_preset_mode": self._saved_preset_mode,
"saved_target_temp": self._saved_target_temp,
"no_presence_preset": self._no_presence_preset
if self._presence_on
else None,
"no_presence_offset": self._no_presence_offset
if self._presence_on
else None,
"window_state": self._window_state,
"motion_state": self._motion_state,
"overpowering_state": self._overpowering_state,
@@ -1327,3 +1371,49 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
Note: this don't work either
"""
_LOGGER.info("%s - The config entry have been updated.")
async def service_set_presence(self, presence):
"""Called by a service call:
service: versatile_thermostat.set_presence
data:
presence: "off"
target:
entity_id: climate.thermostat_1
"""
_LOGGER.info("%s - Calling service_set_presence, presence: %s", self, presence)
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)
@@ -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,
@@ -33,17 +36,14 @@ from .const import (
CONF_NO_MOTION_PRESET,
CONF_DEVICE_POWER,
CONF_CYCLE_MIN,
CONF_PRESET_POWER,
CONF_PRESETS,
CONF_PRESETS_AWAY,
CONF_PRESETS_SELECTIONABLE,
CONF_PROP_FUNCTION,
CONF_PROP_BIAS,
CONF_TPI_COEF_T,
CONF_TPI_COEF_C,
CONF_TPI_COEF_EXT,
CONF_TPI_COEF_INT,
CONF_PRESENCE_SENSOR,
CONF_NO_PRESENCE_PRESET,
CONF_NO_PRESENCE_TEMP_OFFSET,
PROPORTIONAL_FUNCTION_ATAN,
PROPORTIONAL_FUNCTION_LINEAR,
PROPORTIONAL_FUNCTION_TPI,
)
@@ -51,121 +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_CYCLE_MIN, default=5): cv.positive_int,
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_LINEAR): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
PROPORTIONAL_FUNCTION_LINEAR,
PROPORTIONAL_FUNCTION_ATAN,
]
),
}
)
# USER_DATA_CONF = [
# CONF_NAME,
# CONF_HEATER,
# CONF_TEMP_SENSOR,
# CONF_EXTERNAL_TEMP_SENSOR,
# CONF_CYCLE_MIN,
# CONF_PROP_FUNCTION,
# ]
STEP_P_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_PROP_BIAS, default=0.25): vol.Coerce(float),
}
)
# P_DATA_CONF = [
# CONF_PROP_BIAS,
# ]
STEP_TPI_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): cv.string,
vol.Required(CONF_TPI_COEF_C, default=0.6): vol.Coerce(float),
vol.Required(CONF_TPI_COEF_T, default=0.01): vol.Coerce(float),
}
)
# TPI_DATA_CONF = [
# CONF_EXTERNAL_TEMP_SENSOR,
# CONF_TPI_COEF_C,
# CONF_TPI_COEF_T,
# ]
STEP_PRESETS_DATA_SCHEMA = vol.Schema(
{vol.Optional(v, default=17): vol.Coerce(float) for (k, v) in CONF_PRESETS.items()}
)
# PRESETS_DATA_CONF = [v for (_, 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,
}
)
# WINDOW_DATA_CONF = [CONF_WINDOW_SENSOR, CONF_WINDOW_DELAY]
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
),
}
)
# MOTION_DATA_CONF = [
# CONF_MOTION_SENSOR,
# CONF_MOTION_DELAY,
# CONF_MOTION_PRESET,
# CONF_NO_MOTION_PRESET,
# ]
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),
}
)
# POWER_DATA_CONF = [CONF_POWER_SENSOR, CONF_MAX_POWER_SENSOR, CONF_DEVICE_POWER]
STEP_PRESENCE_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_PRESENCE_SENSOR): cv.string,
vol.Optional(CONF_NO_PRESENCE_PRESET): vol.In(CONF_PRESETS_SELECTIONABLE),
vol.Optional(CONF_NO_PRESENCE_TEMP_OFFSET): vol.Coerce(float),
}
)
# PRESENCE_DATA_CONF = [CONF_PRESENCE_SENSOR, CONF_NO_PRESENCE_PRESET, CONF_NO_PRESENCE_TEMP_OFFSET]
# 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):
@@ -178,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.
@@ -204,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(
@@ -223,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)
@@ -235,24 +248,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_user user_input=%s", user_input)
async def choose_next_step():
"""Choose next configuration flow"""
_LOGGER.debug("function is %s", self._infos.get(CONF_PROP_FUNCTION, None))
if self._infos.get(CONF_PROP_FUNCTION, None) == PROPORTIONAL_FUNCTION_TPI:
return await self.async_step_tpi()
else:
return await self.async_step_p()
return await self.generic_step(
"user", STEP_USER_DATA_SCHEMA, user_input, choose_next_step
)
async def async_step_p(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_p user_input=%s", user_input)
return await self.generic_step(
"p", STEP_P_DATA_SCHEMA, user_input, self.async_step_presets
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
@@ -260,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:
@@ -268,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:
@@ -276,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:
@@ -284,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:
@@ -293,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,
)
@@ -304,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
)
@@ -328,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)
@@ -366,24 +363,8 @@ class VersatileThermostatOptionsFlowHandler(
"Into OptionsFlowHandler.async_step_user user_input=%s", user_input
)
async def choose_next_step():
"""Choose next configuration flow"""
_LOGGER.debug("function is %s", self._infos.get(CONF_PROP_FUNCTION, None))
if self._infos.get(CONF_PROP_FUNCTION, None) == PROPORTIONAL_FUNCTION_TPI:
return await self.async_step_tpi()
else:
return await self.async_step_p()
return await self.generic_step(
"user", STEP_USER_DATA_SCHEMA, user_input, choose_next_step
)
async def async_step_p(self, user_input: dict | None = None) -> FlowResult:
"""Handle the p flow steps"""
_LOGGER.debug("Into OptionsFlowHandler.async_step_p user_input=%s", user_input)
return await self.generic_step(
"p", STEP_P_DATA_SCHEMA, user_input, self.async_step_presets
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
@@ -393,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:
@@ -403,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:
@@ -413,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:
@@ -423,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:
@@ -434,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
)
@@ -447,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
)
+24 -20
View File
@@ -3,7 +3,6 @@
from homeassistant.const import CONF_NAME
from homeassistant.components.climate.const import (
# PRESET_ACTIVITY,
PRESET_AWAY,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
@@ -11,8 +10,6 @@ from homeassistant.components.climate.const import (
)
from .prop_algorithm import (
PROPORTIONAL_FUNCTION_ATAN,
PROPORTIONAL_FUNCTION_LINEAR,
PROPORTIONAL_FUNCTION_TPI,
)
@@ -30,31 +27,39 @@ CONF_MOTION_SENSOR = "motion_sensor_entity_id"
CONF_DEVICE_POWER = "device_power"
CONF_CYCLE_MIN = "cycle_min"
CONF_PROP_FUNCTION = "proportional_function"
CONF_PROP_BIAS = "proportional_bias"
CONF_WINDOW_DELAY = "window_delay"
CONF_MOTION_DELAY = "motion_delay"
CONF_MOTION_PRESET = "motion_preset"
CONF_NO_MOTION_PRESET = "no_motion_preset"
CONF_TPI_COEF_C = "tpi_coefc"
CONF_TPI_COEF_T = "tpi_coeft"
CONF_TPI_COEF_INT = "tpi_coef_int"
CONF_TPI_COEF_EXT = "tpi_coef_ext"
CONF_PRESENCE_SENSOR = "presence_sensor_entity_id"
CONF_NO_PRESENCE_PRESET = "no_presence_preset"
CONF_NO_PRESENCE_TEMP_OFFSET = "no_presence_temp_offset"
CONF_PRESET_POWER = "power_temp"
CONF_PRESETS = {
p: f"{p}_temp"
for p in (
PRESET_ECO,
PRESET_AWAY,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_POWER,
PRESET_BOOST,
)
}
CONF_PRESETS_SELECTIONABLE = [PRESET_ECO, PRESET_COMFORT, PRESET_AWAY, PRESET_BOOST]
PRESET_AWAY_SUFFIX = "_away"
CONF_PRESETS_AWAY = {
p: f"{p}_temp"
for p in (
PRESET_ECO + PRESET_AWAY_SUFFIX,
PRESET_BOOST + PRESET_AWAY_SUFFIX,
PRESET_COMFORT + PRESET_AWAY_SUFFIX,
)
}
CONF_PRESETS_SELECTIONABLE = [PRESET_ECO, PRESET_COMFORT, PRESET_BOOST]
CONF_PRESETS_VALUES = list(CONF_PRESETS.values())
CONF_PRESETS_AWAY_VALUES = list(CONF_PRESETS_AWAY.values())
ALL_CONF = (
[
@@ -73,20 +78,19 @@ ALL_CONF = (
CONF_DEVICE_POWER,
CONF_CYCLE_MIN,
CONF_PROP_FUNCTION,
CONF_PROP_BIAS,
CONF_TPI_COEF_C,
CONF_TPI_COEF_T,
CONF_TPI_COEF_INT,
CONF_TPI_COEF_EXT,
CONF_PRESENCE_SENSOR,
CONF_NO_PRESENCE_PRESET,
CONF_NO_PRESENCE_TEMP_OFFSET,
]
+ CONF_PRESETS_VALUES,
+ CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES,
)
CONF_FUNCTIONS = [
PROPORTIONAL_FUNCTION_LINEAR,
PROPORTIONAL_FUNCTION_ATAN,
PROPORTIONAL_FUNCTION_TPI,
]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
@@ -16,22 +16,24 @@ class PropAlgorithm:
"""This class aims to do all calculation of the Proportional alogorithm"""
def __init__(
self, function_type: str, bias: float, tpi_coefc, tpi_coeft, cycle_min: int
self,
function_type: str,
tpi_coef_int,
tpi_coef_ext,
cycle_min: int,
):
"""Initialisation of the Proportional Algorithm"""
_LOGGER.debug(
"Creation new PropAlgorithm function_type: %s, bias: %s, tpi_coefc: %s, tpi_coeft: %s, cycle_min:%d",
"Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d",
function_type,
bias,
tpi_coefc,
tpi_coeft,
tpi_coef_int,
tpi_coef_ext,
cycle_min,
)
# TODO test function_type, bias, cycle_min
self._function = function_type
self._bias = bias
self._tpi_coefc = tpi_coefc
self._tpi_coeft = tpi_coeft
self._tpi_coef_int = tpi_coef_int
self._tpi_coef_ext = tpi_coef_ext
self._cycle_min = cycle_min
self._on_percent = 0
self._on_time_sec = 0
@@ -52,13 +54,10 @@ class PropAlgorithm:
target_temp - ext_current_temp if ext_current_temp is not None else 0
)
if self._function == PROPORTIONAL_FUNCTION_LINEAR:
self._on_percent = 0.25 * delta_temp + self._bias
elif self._function == PROPORTIONAL_FUNCTION_ATAN:
self._on_percent = math.atan(delta_temp + self._bias) / 1.4
elif self._function == PROPORTIONAL_FUNCTION_TPI:
if self._function == PROPORTIONAL_FUNCTION_TPI:
self._on_percent = (
self._tpi_coefc * delta_temp + self._tpi_coeft * delta_ext_temp
self._tpi_coef_int * delta_temp
+ self._tpi_coef_ext * delta_ext_temp
)
else:
_LOGGER.warning(
@@ -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
@@ -10,35 +10,26 @@
"name": "Name",
"heater_entity_id": "Heater entity id",
"temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Function to use (linear is less aggressive)"
}
},
"p": {
"title": "Proportional",
"description": "Proportional attributes",
"data": {
"proportional_bias": "A bias to use in proportional algorithm"
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
},
"tpi": {
"title": "TPI",
"description": "Time Proportional Integral attributes",
"data": {
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"tpi_coefc": "Coefficient to use for internal temperature delta",
"tpi_coeft": "Coefficient to use for external temperature delta"
"tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta"
}
},
"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",
"away_temp": "Temperature in Away preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset",
"power_temp": "Temperature in Power (overpowering) preset"
"boost_temp": "Temperature in Boost preset"
}
},
"window": {
@@ -65,7 +56,8 @@
"data": {
"power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id",
"device_power": "Device power (kW)"
"device_power": "Device power (kW)",
"power_temp": "Temperature for Power shedding"
}
},
"presence": {
@@ -73,8 +65,9 @@
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"no_presence_preset": "Preset to use when no one is present",
"no_presence_offset": "Temperature offset to apply to current temperature is no one is present"
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
}
}
},
@@ -96,35 +89,26 @@
"name": "Name",
"heater_entity_id": "Heater entity id",
"temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Function to use (linear is less aggressive)"
}
},
"p": {
"title": "Proportional",
"description": "Proportional attributes",
"data": {
"proportional_bias": "A bias to use in proportional algorithm"
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
},
"tpi": {
"title": "TPI",
"description": "Time Proportional Integral attributes",
"data": {
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"tpi_coefc": "Coefficient to use for internal temperature delta",
"tpi_coeft": "Coefficient to use for external temperature delta"
"tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta"
}
},
"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",
"away_temp": "Temperature in Away preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset",
"power_temp": "Temperature in Power (overpowering) preset"
"boost_temp": "Temperature in Boost preset"
}
},
"window": {
@@ -151,7 +135,8 @@
"data": {
"power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id",
"device_power": "Device power (kW)"
"device_power": "Device power (kW)",
"power_temp": "Temperature for Power shedding"
}
},
"presence": {
@@ -159,8 +144,9 @@
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"no_presence_preset": "Preset to use when no one is present",
"no_presence_offset": "Temperature offset to apply to current temperature is no one is present"
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
}
}
},
@@ -10,35 +10,26 @@
"name": "Name",
"heater_entity_id": "Heater entity id",
"temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Function to use (linear is less aggressive)"
}
},
"p": {
"title": "Proportional",
"description": "Proportional attributes",
"data": {
"proportional_bias": "A bias to use in proportional algorithm"
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
},
"tpi": {
"title": "TPI",
"description": "Time Proportional Integral attributes",
"data": {
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"tpi_coefc": "Coefficient to use for internal temperature delta",
"tpi_coeft": "Coefficient to use for external temperature delta"
"tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta"
}
},
"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",
"away_temp": "Temperature in Away preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset",
"power_temp": "Temperature in Power (overpowering) preset"
"boost_temp": "Temperature in Boost preset"
}
},
"window": {
@@ -65,7 +56,8 @@
"data": {
"power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id",
"device_power": "Device power (kW)"
"device_power": "Device power (kW)",
"power_temp": "Temperature for Power shedding"
}
},
"presence": {
@@ -73,8 +65,9 @@
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"no_presence_preset": "Preset to use when no one is present",
"no_presence_offset": "Temperature offset to apply to current temperature is no one is present"
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
}
}
},
@@ -96,35 +89,26 @@
"name": "Name",
"heater_entity_id": "Heater entity id",
"temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Function to use (linear is less aggressive)"
}
},
"p": {
"title": "Proportional",
"description": "Proportional attributes",
"data": {
"proportional_bias": "A bias to use in proportional algorithm"
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
},
"tpi": {
"title": "TPI",
"description": "Time Proportional Integral attributes",
"data": {
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
"tpi_coefc": "Coefficient to use for internal temperature delta",
"tpi_coeft": "Coefficient to use for external temperature delta"
"tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta"
}
},
"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",
"away_temp": "Temperature in Away preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset",
"power_temp": "Temperature in Power (overpowering) preset"
"boost_temp": "Temperature in Boost preset"
}
},
"window": {
@@ -151,7 +135,8 @@
"data": {
"power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id",
"device_power": "Device power (kW)"
"device_power": "Device power (kW)",
"power_temp": "Temperature for Power shedding"
}
},
"presence": {
@@ -159,8 +144,9 @@
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"no_presence_preset": "Preset to use when no one is present",
"no_presence_offset": "Temperature offset to apply to current temperature is no one is present"
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
}
}
},
@@ -10,35 +10,26 @@
"name": "Nom",
"heater_entity_id": "Radiateur entity id",
"temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)",
"proportional_function": "Fonction de l'algorithm proportionnel à utiliser (linear est moins aggressive)"
}
},
"p": {
"title": "Proportional",
"description": "Attributs des algos Proportionnel",
"data": {
"proportional_bias": "Un biais à utiliser dans l'algorithm proportionnel"
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)"
}
},
"tpi": {
"title": "TPI",
"description": "Attributs de l'algo Time Proportional Integral",
"data": {
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"tpi_coefc": "coeff_c : Coefficient à utiliser pour le delta de température interne",
"tpi_coeft": "coeff_t : Coefficient à utiliser pour le delta de température externe"
"tpi_coef_int": "coeff_int : Coefficient à utiliser pour le delta de température interne",
"tpi_coef_ext": "coeff_ext : Coefficient à utiliser pour le delta de température externe"
}
},
"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",
"away_temp": "Température en preset Away",
"comfort_temp": "Température en preset Comfort",
"boost_temp": "Température en preset Boost",
"power_temp": "Température en preset Power (overpowering)"
"boost_temp": "Température en preset Boost"
}
},
"window": {
@@ -65,7 +56,8 @@
"data": {
"power_sensor_entity_id": "Capteur de puissance totale (entity id)",
"max_power_sensor_entity_id": "Capteur de puissance Max (entity id)",
"device_power": "Puissance de l'équipement"
"device_power": "Puissance de l'équipement",
"power_temp": "Température si délestaqe"
}
},
"presence": {
@@ -73,8 +65,9 @@
"description": "Donnez un capteur de présence (true si quelqu'un est présent).\nEnsuite spécifiez soit un preset à utiliser, soit un offset de température à appliquer lorsque personne n'est présent.\nSi le préset est utilisé, l'offset ne sera pas pris en compte.\nLaissez l'entity id vide si la gestion de la présence est non utilisée.",
"data": {
"presence_sensor_entity_id": "Capteur de présence entity id (true si quelqu'un est présent)",
"no_presence_preset": "Preset à utiliser si personne n'est présent",
"no_presence_offset": "Offset de température à utiliser si personne n'est présent"
"eco_away_temp": "Température en preset Eco en cas d'absence",
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
"boost_away_temp": "Température en preset Boost en cas d'absence"
}
}
},
@@ -96,35 +89,26 @@
"name": "Nom",
"heater_entity_id": "Radiateur entity id",
"temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)",
"proportional_function": "Fonction de l'algorithm proportionnel à utiliser (linear est moins aggressive)"
}
},
"p": {
"title": "Proportional",
"description": "Attributs des algos Proportionnel",
"data": {
"proportional_bias": "Un biais à utiliser dans l'algorithm proportionnel"
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)"
}
},
"tpi": {
"title": "TPI",
"description": "Attributs de l'algo Time Proportional Integral",
"data": {
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"tpi_coefc": "coeff_c : Coefficient à utiliser pour le delta de température interne",
"tpi_coeft": "coeff_t : Coefficient à utiliser pour le delta de température externe"
"tpi_coef_int": "coeff_int : Coefficient à utiliser pour le delta de température interne",
"tpi_coef_ext": "coeff_ext : Coefficient à utiliser pour le delta de température externe"
}
},
"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",
"away_temp": "Température en preset Away",
"comfort_temp": "Température en preset Comfort",
"boost_temp": "Température en preset Boost",
"power_temp": "Température en preset Power (overpowering)"
"boost_temp": "Température en preset Boost"
}
},
"window": {
@@ -151,7 +135,8 @@
"data": {
"power_sensor_entity_id": "Capteur de puissance totale (entity id)",
"max_power_sensor_entity_id": "Capteur de puissance Max (entity id)",
"device_power": "Puissance de l'équipement"
"device_power": "Puissance de l'équipement",
"power_temp": "Température si délestaqe"
}
},
"presence": {
@@ -159,8 +144,9 @@
"description": "Donnez un capteur de présence (true si quelqu'un est présent).\nEnsuite spécifiez soit un preset à utiliser, soit un offset de température à appliquer lorsque personne n'est présent.\nSi le préset est utilisé, l'offset ne sera pas pris en compte.\nLaissez l'entity id vide si la gestion de la présence est non utilisée.",
"data": {
"presence_sensor_entity_id": "Capteur de présence entity id (true si quelqu'un est présent)",
"no_presence_preset": "Preset à utiliser si personne n'est présent",
"no_presence_offset": "Offset de température à utiliser si personne n'est présent"
"eco_away_temp": "Température en preset Eco en cas d'absence",
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
"boost_away_temp": "Température en preset Boost en cas d'absence"
}
}
},