Compare commits

..

7 Commits
6.5.0 ... 6.6.0

Author SHA1 Message Date
Jean-Marc Collin
027bf8386b Add message into issue template. 2024-11-05 06:37:27 +00:00
Jean-Marc Collin
a0e548ef71 Release 2024-11-03 22:16:15 +00:00
Jean-Marc Collin
132519b471 Merge #590 and fix some tests 2024-11-03 22:12:56 +00:00
hilburn
e6c330fc9d Underlying config changes (#590)
* Changes config_flow to allow dynamic length list of underlying entities
Updates previously defined 4x entries to new config style
Changes to thermostat_X to load underlying entities from list
Changes to thermostat X to display underlying entities as a list - COULD BREAK EXISTING TEMPLATES

* Modifies tests to use the new list format

* Added English translation for UI

* Removed all references to individual entities in strings/en.json

* Fix merge mistake

---------

Co-authored-by: Jean-Marc Collin <jm.collin.78@gmail.com>
2024-11-03 22:52:19 +01:00
Jean-Marc Collin
968e8286ea Add some infos 2024-11-03 21:50:42 +00:00
hilburn
0f60c070ab Preset display tweaks (#599)
* Addded Frost Preset to translations
Added Icons for Shedding, Safety, Manual and Frost Presets

* Fixed French Translation
2024-11-03 11:50:37 +01:00
Jean-Marc Collin
810430f7b1 Update README.md
#597
2024-11-02 19:16:24 +01:00
19 changed files with 203 additions and 293 deletions

View File

@@ -4,6 +4,8 @@ about: Create a report to help us improve
---
> Please read carefuly this instructions and fill this form before writing an issue. It helps me to help you.
<!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. -->
<!-- Before you open a new issue, search through the existing issues to see if others have had the same problem.

View File

@@ -1172,9 +1172,9 @@ Custom attributes are the following:
| ``is_controlled_by_central_mode`` | True if the VTherm can be centrally controlled |
| ``last_central_mode`` | The last central mode used (None if the VTherm is not centrally controlled) |
| ``is_used_by_central_boiler`` | Indicate if the VTherm can control the central boiler |
| ``auto_start_stop_enable`` | Indique si le VTherm est autorisé à s'auto démarrer/arrêter |
| ``auto_start_stop_level`` | Indique le niveau d'auto start/stop |
| ``hvac_off_reason`` | Indique la raison de l'arrêt (hvac_off) du VTherm. Ce peut être Window, Auto-start/stop ou Manuel |
| ``auto_start_stop_enable`` | Indicate if the VTherm is allowed to do auto start and stop |
| ``auto_start_stop_level`` | Give the level of auto start/stop |
| ``hvac_off_reason`` | Give the reason of stop of the VTherm. This could be Window, Auto-start/stop or Manual |
# Some results

View File

@@ -38,6 +38,22 @@ from .const import (
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_POWER_SENSOR,
CONF_PRESENCE_SENSOR,
CONF_UNDERLYING_LIST,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
)
from .vtherm_api import VersatileThermostatAPI
@@ -208,10 +224,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
)
new = {**config_entry.data}
if (
config_entry.data.get(CONF_THERMOSTAT_TYPE)
== CONF_THERMOSTAT_CENTRAL_CONFIG
):
thermostat_type = config_entry.data.get(CONF_THERMOSTAT_TYPE)
if thermostat_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
new[CONF_USE_WINDOW_FEATURE] = True
new[CONF_USE_MOTION_FEATURE] = True
new[CONF_USE_POWER_FEATURE] = new.get(CONF_POWER_SENSOR, None) is not None
@@ -223,6 +238,50 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"add_central_boiler_control", False
) or new.get(CONF_USE_CENTRAL_BOILER_FEATURE, False)
if config_entry.data.get(CONF_UNDERLYING_LIST, None) is None:
underlying_list = []
if thermostat_type == CONF_THERMOSTAT_SWITCH:
underlying_list = [
config_entry.data.get(CONF_HEATER, None),
config_entry.data.get(CONF_HEATER_2, None),
config_entry.data.get(CONF_HEATER_3, None),
config_entry.data.get(CONF_HEATER_4, None),
]
elif thermostat_type == CONF_THERMOSTAT_CLIMATE:
underlying_list = [
config_entry.data.get(CONF_CLIMATE, None),
config_entry.data.get(CONF_CLIMATE_2, None),
config_entry.data.get(CONF_CLIMATE_3, None),
config_entry.data.get(CONF_CLIMATE_4, None),
]
elif thermostat_type == CONF_THERMOSTAT_VALVE:
underlying_list = [
config_entry.data.get(CONF_VALVE, None),
config_entry.data.get(CONF_VALVE_2, None),
config_entry.data.get(CONF_VALVE_3, None),
config_entry.data.get(CONF_VALVE_4, None),
]
new[CONF_UNDERLYING_LIST] = [
entity for entity in underlying_list if entity is not None
]
for key in [
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
]:
new.pop(key, None)
hass.config_entries.async_update_entry(
config_entry,
data=new,

View File

@@ -165,7 +165,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
# check the heater_entity_id
for conf in [
CONF_HEATER,
CONF_UNDERLYING_LIST,
CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_WINDOW_SENSOR,
@@ -173,15 +173,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR,
CONF_PRESENCE_SENSOR,
CONF_CLIMATE,
]:
d = data.get(conf, None) # pylint: disable=invalid-name
if d is not None and self.hass.states.get(d) is None:
_LOGGER.error(
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
d,
)
raise UnknownEntity(conf)
if not isinstance(d, list):
d = [d]
for e in d:
if e is not None and self.hass.states.get(e) is None:
_LOGGER.error(
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
e,
)
raise UnknownEntity(conf)
# Check that only one window feature is used
ws = self._infos.get(CONF_WINDOW_SENSOR) # pylint: disable=invalid-name
@@ -270,21 +272,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_SWITCH
and infos.get(CONF_HEATER, None) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
and infos.get(CONF_CLIMATE, None) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
and infos.get(CONF_VALVE, None) is None
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
CONF_UNDERLYING_LIST, None
):
return False

View File

@@ -119,17 +119,10 @@ STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_HEATER): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN], multiple=True
),
),
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
@@ -144,17 +137,8 @@ STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_CLIMATE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True),
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(
@@ -183,17 +167,10 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_VALVE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[

View File

@@ -23,8 +23,8 @@ from .prop_algorithm import (
_LOGGER = logging.getLogger(__name__)
CONFIG_VERSION = 1
CONFIG_MINOR_VERSION = 2
CONFIG_VERSION = 2
CONFIG_MINOR_VERSION = 0
PRESET_TEMP_SUFFIX = "_temp"
PRESET_AC_SUFFIX = "_ac"
@@ -55,10 +55,7 @@ PLATFORMS: list[Platform] = [
Platform.SWITCH,
]
CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id"
CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_UNDERLYING_LIST = "underlying_entity_ids"
CONF_HEATER_KEEP_ALIVE = "heater_keep_alive"
CONF_TEMP_SENSOR = "temperature_sensor_entity_id"
CONF_LAST_SEEN_TEMP_SENSOR = "last_seen_temperature_sensor_entity_id"
@@ -90,10 +87,6 @@ CONF_THERMOSTAT_CENTRAL_CONFIG = "thermostat_central_config"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
CONF_THERMOSTAT_VALVE = "thermostat_over_valve"
CONF_CLIMATE = "climate_entity_id"
CONF_CLIMATE_2 = "climate_entity2_id"
CONF_CLIMATE_3 = "climate_entity3_id"
CONF_CLIMATE_4 = "climate_entity4_id"
CONF_USE_WINDOW_FEATURE = "use_window_feature"
CONF_USE_MOTION_FEATURE = "use_motion_feature"
CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
@@ -104,10 +97,6 @@ CONF_AC_MODE = "ac_mode"
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
CONF_VALVE = "valve_entity_id"
CONF_VALVE_2 = "valve_entity2_id"
CONF_VALVE_3 = "valve_entity3_id"
CONF_VALVE_4 = "valve_entity4_id"
CONF_AUTO_REGULATION_MODE = "auto_regulation_mode"
CONF_AUTO_REGULATION_NONE = "auto_regulation_none"
CONF_AUTO_REGULATION_SLOW = "auto_regulation_slow"
@@ -127,6 +116,20 @@ CONF_AUTO_FAN_HIGH = "auto_fan_high"
CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
CONF_STEP_TEMPERATURE = "step_temperature"
# Deprecated
CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id"
CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_CLIMATE = "climate_entity_id"
CONF_CLIMATE_2 = "climate_entity2_id"
CONF_CLIMATE_3 = "climate_entity3_id"
CONF_CLIMATE_4 = "climate_entity4_id"
CONF_VALVE = "valve_entity_id"
CONF_VALVE_2 = "valve_entity2_id"
CONF_VALVE_3 = "valve_entity3_id"
CONF_VALVE_4 = "valve_entity4_id"
# Global params into configuration.yaml
CONF_SHORT_EMA_PARAMS = "short_ema_params"
CONF_SAFETY_MODE = "safety_mode"
@@ -249,10 +252,6 @@ CONF_PRESETS_AWAY_WITH_AC_VALUES = list(CONF_PRESETS_AWAY_WITH_AC.values())
ALL_CONF = (
[
CONF_NAME,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_HEATER_KEEP_ALIVE,
CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
@@ -282,20 +281,12 @@ ALL_CONF = (
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_AC_MODE,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,

View File

@@ -0,0 +1,18 @@
{
"entity": {
"climate": {
"versatile_thermostat": {
"state_attributes": {
"preset_mode": {
"state": {
"shedding": "mdi:power-plug-off",
"safety": "mdi:shield-alert",
"none": "mdi:knob",
"frost": "mdi:snowflake"
}
}
}
}
}
}
}

View File

@@ -14,6 +14,6 @@
"quality_scale": "silver",
"requirements": [],
"ssdp": [],
"version": "6.5.0",
"version": "6.6.0",
"zeroconf": []
}

View File

@@ -72,21 +72,10 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -95,21 +84,10 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not required",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not required",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not required",
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -309,21 +287,10 @@
"title": "Entities - {name}",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -332,21 +299,10 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -536,7 +492,8 @@
"state": {
"power": "Shedding",
"security": "Safety",
"none": "Manual"
"none": "Manual",
"frost": "Frost"
}
}
}

View File

@@ -58,7 +58,7 @@ class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEn
@property
def icon(self) -> str | None:
"""The icon"""
return "mdi:power-settings"
return "mdi:power-sleep"
async def async_added_to_hass(self):
await super().async_added_to_hass()

View File

@@ -65,10 +65,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
{
"is_over_climate",
"start_hvac_action_date",
"underlying_climate_0",
"underlying_climate_1",
"underlying_climate_2",
"underlying_climate_3",
"underlying_entities",
"regulation_accumulated_error",
"auto_regulation_mode",
"auto_fan_mode",
@@ -100,20 +97,15 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"""Initialize the Thermostat"""
super().post_init(config_entry)
for climate in [
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
]:
if config_entry.get(climate):
self._underlyings.append(
UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=config_entry.get(climate),
)
for climate in config_entry.get(CONF_UNDERLYING_LIST):
self._underlyings.append(
UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=climate,
)
)
self.choose_auto_regulation_mode(
config_entry.get(CONF_AUTO_REGULATION_MODE)
@@ -504,18 +496,10 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self._attr_extra_state_attributes["start_hvac_action_date"] = (
self._underlying_climate_start_hvac_action_date
)
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
0
].entity_id
self._attr_extra_state_attributes["underlying_climate_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_climate_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_climate_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes["underlying_entities"] = [
underlying.entity_id for underlying in self._underlyings
]
if self.is_regulated:
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
@@ -910,6 +894,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
@@ -933,6 +919,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)

View File

@@ -10,10 +10,7 @@ from homeassistant.helpers.event import (
from homeassistant.components.climate import HVACMode
from .const import (
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_UNDERLYING_LIST,
CONF_HEATER_KEEP_ALIVE,
CONF_INVERSE_SWITCH,
overrides,
@@ -35,10 +32,7 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
{
"is_over_switch",
"is_inversed",
"underlying_switch_0",
"underlying_switch_1",
"underlying_switch_2",
"underlying_switch_3",
"underlying_entities",
"on_time_sec",
"off_time_sec",
"cycle_min",
@@ -90,13 +84,7 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self.name,
)
lst_switches = [config_entry.get(CONF_HEATER)]
if config_entry.get(CONF_HEATER_2):
lst_switches.append(config_entry.get(CONF_HEATER_2))
if config_entry.get(CONF_HEATER_3):
lst_switches.append(config_entry.get(CONF_HEATER_3))
if config_entry.get(CONF_HEATER_4):
lst_switches.append(config_entry.get(CONF_HEATER_4))
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
delta_cycle = self._cycle_min * 60 / len(lst_switches)
for idx, switch in enumerate(lst_switches):
@@ -140,16 +128,10 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch
self._attr_extra_state_attributes["is_inversed"] = self.is_inversed
self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
self._attr_extra_state_attributes["underlying_switch_0"] = under0.entity_id
self._attr_extra_state_attributes["underlying_switch_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_switch_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_switch_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes["underlying_entities"] = [
underlying.entity_id for underlying in self._underlyings
]
self._attr_extra_state_attributes[
"on_percent"

View File

@@ -15,10 +15,7 @@ from .base_thermostat import BaseThermostat, ConfigData
from .prop_algorithm import PropAlgorithm
from .const import (
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_UNDERLYING_LIST,
# This is not really self-regulation but regulation here
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
@@ -37,10 +34,7 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
frozenset(
{
"is_over_valve",
"underlying_valve_0",
"underlying_valve_1",
"underlying_valve_2",
"underlying_valve_3",
"underlying_entities",
"on_time_sec",
"off_time_sec",
"cycle_min",
@@ -105,13 +99,7 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
self.name,
)
lst_valves = [config_entry.get(CONF_VALVE)]
if config_entry.get(CONF_VALVE_2):
lst_valves.append(config_entry.get(CONF_VALVE_2))
if config_entry.get(CONF_VALVE_3):
lst_valves.append(config_entry.get(CONF_VALVE_3))
if config_entry.get(CONF_VALVE_4):
lst_valves.append(config_entry.get(CONF_VALVE_4))
lst_valves = config_entry.get(CONF_UNDERLYING_LIST)
for _, valve in enumerate(lst_valves):
self._underlyings.append(
@@ -163,18 +151,10 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
"valve_open_percent"
] = self.valve_open_percent
self._attr_extra_state_attributes["is_over_valve"] = self.is_over_valve
self._attr_extra_state_attributes["underlying_valve_0"] = self._underlyings[
0
].entity_id
self._attr_extra_state_attributes["underlying_valve_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_valve_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_valve_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes["underlying_entities"] = [
underlying.entity_id for underlying in self._underlyings
]
self._attr_extra_state_attributes[
"on_percent"

View File

@@ -72,21 +72,10 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -95,21 +84,10 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not required",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not required",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not required",
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -309,21 +287,10 @@
"title": "Entities - {name}",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -332,21 +299,10 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -536,7 +492,8 @@
"state": {
"power": "Shedding",
"security": "Safety",
"none": "Manual"
"none": "Manual",
"frost": "Frost"
}
}
}

View File

@@ -554,7 +554,8 @@
"state": {
"power": "Délestage",
"security": "Sécurité",
"none": "Manuel"
"none": "Manuel",
"frost": "Hors Gel"
}
}
}

View File

@@ -364,7 +364,8 @@
"state": {
"power": "Ripartizione",
"security": "Sicurezza",
"none": "Manuale"
"none": "Manuale",
"frost": "Gelo"
}
}
}

View File

@@ -74,7 +74,7 @@ MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG = {
}
MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
CONF_HEATER: "switch.mock_switch",
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
CONF_HEATER_KEEP_ALIVE: 0,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
@@ -82,17 +82,14 @@ MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
}
MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
CONF_HEATER: "switch.mock_air_conditioner",
CONF_UNDERLYING_LIST: ["switch.mock_air_conditioner"],
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: True,
CONF_INVERSE_SWITCH: False,
}
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
CONF_HEATER: "switch.mock_4switch0",
CONF_HEATER_2: "switch.mock_4switch1",
CONF_HEATER_3: "switch.mock_4switch2",
CONF_HEATER_4: "switch.mock_4switch3",
CONF_UNDERLYING_LIST: ["switch.mock_4switch0", "switch.mock_4switch1","switch.mock_4switch2","switch.mock_4switch3"],
CONF_HEATER_KEEP_ALIVE: 0,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
@@ -105,7 +102,7 @@ MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
}
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_AC_MODE: False,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP: 0.5,
@@ -115,7 +112,7 @@ MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
}
MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG = {
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_AC_MODE: False,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP: 0.1,
@@ -125,13 +122,13 @@ MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG = {
}
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_AC_MODE: False,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_NONE,
}
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG = {
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_AC_MODE: True,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP: 0.5,

View File

@@ -444,6 +444,8 @@ async def test_auto_start_stop_medium_heat_vtherm(
"target_temperature": 19.0,
"current_temperature": 21.0,
"temperature_slope": 0.167,
"accumulated_error": -5,
"accumulated_error_threshold": 5,
},
)
]
@@ -507,6 +509,8 @@ async def test_auto_start_stop_medium_heat_vtherm(
"target_temperature": 19.0,
"current_temperature": 18.0,
"temperature_slope": -0.034,
"accumulated_error": 5,
"accumulated_error_threshold": 5,
},
)
]
@@ -569,7 +573,7 @@ async def test_auto_start_stop_fast_ac_vtherm(
CONF_USE_AUTO_START_STOP_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: True,
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
@@ -672,6 +676,8 @@ async def test_auto_start_stop_fast_ac_vtherm(
"target_temperature": 25.0,
"current_temperature": 23.0,
"temperature_slope": -0.28,
"accumulated_error": 2,
"accumulated_error_threshold": 2,
},
)
]
@@ -734,6 +740,8 @@ async def test_auto_start_stop_fast_ac_vtherm(
"target_temperature": 25.0,
"current_temperature": 26.5,
"temperature_slope": 0.112,
"accumulated_error": -2,
"accumulated_error_threshold": 2,
},
)
]
@@ -881,6 +889,8 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
"target_temperature": 17.0,
"current_temperature": 19.0,
"temperature_slope": 0.3,
"accumulated_error": -2,
"accumulated_error_threshold": 2,
},
)
]
@@ -936,6 +946,8 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
"target_temperature": 21.0,
"current_temperature": 17.0,
"temperature_slope": -0.087,
"accumulated_error": 2,
"accumulated_error_threshold": 2,
},
)
]
@@ -1445,6 +1457,8 @@ async def test_auto_start_stop_fast_heat_window_mixed(
"target_temperature": 19.0,
"current_temperature": 21.0,
"temperature_slope": 0.214,
"accumulated_error": -2,
"accumulated_error_threshold": 2,
},
),
]

View File

@@ -87,7 +87,7 @@ async def test_user_config_flow_over_switch(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HEATER: "switch.mock_switch",
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
CONF_HEATER_KEEP_ALIVE: 0,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
@@ -292,7 +292,7 @@ async def test_user_config_flow_over_switch(
)
assert result["result"]
assert result["result"].domain == DOMAIN
assert result["result"].version == 1
assert result["result"].version == 2
assert result["result"].title == "TheOverSwitchMockName"
assert isinstance(result["result"], ConfigEntry)
@@ -379,7 +379,7 @@ async def test_user_config_flow_over_climate(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_AC_MODE: False,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP: 0.5,
@@ -512,7 +512,7 @@ async def test_user_config_flow_over_climate(
}
assert result["result"]
assert result["result"].domain == DOMAIN
assert result["result"].version == 1
assert result["result"].version == 2
assert result["result"].title == "TheOverClimateMockName"
assert isinstance(result["result"], ConfigEntry)
@@ -794,10 +794,7 @@ async def test_user_config_flow_over_4_switches(
}
TYPE_CONFIG = { # pylint: disable=invalid-name
CONF_HEATER: "switch.mock_switch1",
CONF_HEATER_2: "switch.mock_switch2",
CONF_HEATER_3: "switch.mock_switch3",
CONF_HEATER_4: "switch.mock_switch4",
CONF_UNDERLYING_LIST: ["switch.mock_switch1", "switch.mock_switch2", "switch.mock_switch3","switch.mock_switch4"],
CONF_HEATER_KEEP_ALIVE: 0,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
@@ -1015,7 +1012,7 @@ async def test_user_config_flow_over_climate_auto_start_stop(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_CLIMATE: "climate.mock_climate",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_AC_MODE: False,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP: 0.5,
@@ -1124,6 +1121,6 @@ async def test_user_config_flow_over_climate_auto_start_stop(
}
assert result["result"]
assert result["result"].domain == DOMAIN
assert result["result"].version == 1
assert result["result"].version == 2
assert result["result"].title == "TheOverClimateMockName"
assert isinstance(result["result"], ConfigEntry)