diff --git a/custom_components/versatile_thermostat/__init__.py b/custom_components/versatile_thermostat/__init__.py index f4a6970..5c7c366 100644 --- a/custom_components/versatile_thermostat/__init__.py +++ b/custom_components/versatile_thermostat/__init__.py @@ -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, diff --git a/custom_components/versatile_thermostat/config_flow.py b/custom_components/versatile_thermostat/config_flow.py index ae46080..1601a9c 100644 --- a/custom_components/versatile_thermostat/config_flow.py +++ b/custom_components/versatile_thermostat/config_flow.py @@ -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 diff --git a/custom_components/versatile_thermostat/config_schema.py b/custom_components/versatile_thermostat/config_schema.py index 3d40bfb..4c7b295 100644 --- a/custom_components/versatile_thermostat/config_schema.py +++ b/custom_components/versatile_thermostat/config_schema.py @@ -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( [ diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index 93f88fe..44f4838 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -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, diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index d2c3e98..1d6bf42 100644 --- a/custom_components/versatile_thermostat/strings.json +++ b/custom_components/versatile_thermostat/strings.json @@ -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", diff --git a/custom_components/versatile_thermostat/thermostat_climate.py b/custom_components/versatile_thermostat/thermostat_climate.py index 232d879..1411f27 100644 --- a/custom_components/versatile_thermostat/thermostat_climate.py +++ b/custom_components/versatile_thermostat/thermostat_climate.py @@ -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 diff --git a/custom_components/versatile_thermostat/thermostat_switch.py b/custom_components/versatile_thermostat/thermostat_switch.py index 2e47737..7869d2a 100644 --- a/custom_components/versatile_thermostat/thermostat_switch.py +++ b/custom_components/versatile_thermostat/thermostat_switch.py @@ -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" diff --git a/custom_components/versatile_thermostat/thermostat_valve.py b/custom_components/versatile_thermostat/thermostat_valve.py index d0fb322..7eb7f23 100644 --- a/custom_components/versatile_thermostat/thermostat_valve.py +++ b/custom_components/versatile_thermostat/thermostat_valve.py @@ -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" diff --git a/custom_components/versatile_thermostat/translations/en.json b/custom_components/versatile_thermostat/translations/en.json index d2c3e98..1d6bf42 100644 --- a/custom_components/versatile_thermostat/translations/en.json +++ b/custom_components/versatile_thermostat/translations/en.json @@ -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", diff --git a/tests/const.py b/tests/const.py index 75aa1e7..5214c22 100644 --- a/tests/const.py +++ b/tests/const.py @@ -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, diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 7020d74..c1290f6 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -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, @@ -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,