Issue #113 - Add multi thermostat over climate

This commit is contained in:
Jean-Marc Collin
2023-10-08 01:23:25 +02:00
parent 79eb4a0a0d
commit 72c4105bbd
8 changed files with 145 additions and 36 deletions

View File

@@ -72,6 +72,18 @@ input_boolean:
fake_heater_4switch4:
name: Heater (multiswitch4)
icon: mdi:radiator
fake_heater_4climate1:
name: Heater (multiclimate1)
icon: mdi:radiator
fake_heater_4climate2:
name: Heater (multiclimate2)
icon: mdi:radiator
fake_heater_4climate3:
name: Heater (multiclimate3)
icon: mdi:radiator
fake_heater_4climate4:
name: Heater (multiclimate4)
icon: mdi:radiator
# input_boolean to simulate the motion sensor entity. Only for development environment.
fake_motion_sensor1:
name: Motion Sensor 1
@@ -99,20 +111,20 @@ climate:
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat5
heater: input_boolean.fake_heater_switch3
name: Underlying thermostat 4-1
heater: input_boolean.fake_heater_4climate1
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat6
heater: input_boolean.fake_heater_switch3
name: Underlying thermostat 4-2
heater: input_boolean.fake_heater_4climate2
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat7
heater: input_boolean.fake_heater_switch3
name: Underlying thermostat 4-3
heater: input_boolean.fake_heater_4climate3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat8
heater: input_boolean.fake_heater_switch3
name: Underlying thermostat 4-4
heater: input_boolean.fake_heater_4climate4
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat9

View File

@@ -129,6 +129,9 @@ from .const import (
# CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_AC_MODE,
UnknownEntity,
EventType,
@@ -328,16 +331,19 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._cycle_min = entry_infos.get(CONF_CYCLE_MIN)
# Initialize underlying entities
self._underlyings = []
self._thermostat_type = entry_infos.get(CONF_THERMOSTAT_TYPE)
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
self._is_over_climate = True
self._underlyings.append(
UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=entry_infos.get(CONF_CLIMATE),
)
)
for climate in [CONF_CLIMATE, CONF_CLIMATE_2, CONF_CLIMATE_3, CONF_CLIMATE_4]:
if entry_infos.get(climate):
self._underlyings.append(
UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=entry_infos.get(climate),
)
)
else:
lst_switches = [entry_infos.get(CONF_HEATER)]
if entry_infos.get(CONF_HEATER_2):
@@ -348,7 +354,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
lst_switches.append(entry_infos.get(CONF_HEATER_4))
delta_cycle = self._cycle_min * 60 / len(lst_switches)
self._underlyings = []
for idx, switch in enumerate(lst_switches):
self._underlyings.append(
UnderlyingSwitch(
@@ -1582,6 +1587,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if not new_state:
return
changes = False
new_hvac_mode = new_state.state
old_state = event.data.get("old_state")
@@ -1597,9 +1603,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE:
_LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF")
new_hvac_mode = HVACMode.OFF
#if self._hvac_mode == HVACMode.OFF and new_hvac_mode == HVACMode.COOL and new_hvac_action == HVACAction.IDLE:
# _LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF")
# new_hvac_mode = HVACMode.OFF
_LOGGER.info(
"%s - Underlying climate changed. Event.new_hvac_mode is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s",
@@ -1619,8 +1625,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
HVACMode.AUTO,
HVACMode.FAN_ONLY,
None
]:
] and self._hvac_mode != new_hvac_mode:
changes = True
self._hvac_mode = new_hvac_mode
# Do not try to update all underlying state, else we will have a loop
if self._is_over_climate:
for under in self._underlyings:
await under.set_hvac_mode(new_hvac_mode)
# Interpretation of hvac
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
@@ -1638,6 +1649,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self,
self._underlying_climate_start_hvac_action_date.isoformat(),
)
changes = True
if old_hvac_action in HVAC_ACTION_ON and new_hvac_action not in HVAC_ACTION_ON:
stop_power_date = self.get_last_updated_date_or_now(new_state)
@@ -1658,14 +1670,20 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
stop_power_date.isoformat(),
self._underlying_climate_delta_t,
)
changes = True
# Manage new target temperature set
if self._is_over_climate and new_state.attributes and (new_target_temp := new_state.attributes.get("temperature")) and new_target_temp != self.target_temperature:
_LOGGER.info("%s - Target temp have change to %s", self, new_target_temp)
await self.async_set_temperature(temperature = new_target_temp)
if not changes:
# try to manage new target temperature set if state
_LOGGER.debug("Do temperature check. temperature is %s, new_state.attributes is %s", self.target_temperature, new_state.attributes)
if self._is_over_climate and new_state.attributes and (new_target_temp := new_state.attributes.get("temperature")) and new_target_temp != self.target_temperature:
_LOGGER.info("%s - Target temp have change to %s", self, new_target_temp)
await self.async_set_temperature(temperature = new_target_temp)
changes = True
self.update_custom_attributes()
await self._async_control_heating()
if changes:
self.async_write_ha_state()
self.update_custom_attributes()
await self._async_control_heating()
@callback
async def _async_update_temp(self, state: State):
@@ -2364,9 +2382,19 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"window_auto_max_duration": self._window_auto_max_duration,
}
if self._is_over_climate:
self._attr_extra_state_attributes["underlying_climate"] = self._underlyings[
self._attr_extra_state_attributes["underlying_climate_1"] = 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[
"start_hvac_action_date"
] = self._underlying_climate_start_hvac_action_date

View File

@@ -81,6 +81,9 @@ from .const import (
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
@@ -231,6 +234,15 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
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.Optional(CONF_AC_MODE, default=False): cv.boolean,
}
)

View File

@@ -65,6 +65,9 @@ CONF_THERMOSTAT_TYPE = "thermostat_type"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
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"
@@ -130,6 +133,9 @@ ALL_CONF = (
[
CONF_NAME,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_POWER_SENSOR,
@@ -159,6 +165,9 @@ ALL_CONF = (
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,

View File

@@ -26,11 +26,14 @@
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "Heater switch",
"heater_entity2_id": "2nd Heater switch",
"heater_entity3_id": "3rd Heater switch",
"heater_entity4_id": "4th Heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat",
"climate_entity_id": "Underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode"
},
"data_description": {
@@ -40,6 +43,9 @@
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"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"
}
},
@@ -170,6 +176,9 @@
"heater_entity4_id": "4th Heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode"
},
"data_description": {
@@ -179,6 +188,9 @@
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"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"
}
},

View File

@@ -26,11 +26,14 @@
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "Heater switch",
"heater_entity2_id": "2nd Heater switch",
"heater_entity3_id": "3rd Heater switch",
"heater_entity4_id": "4th Heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat",
"climate_entity_id": "Underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode"
},
"data_description": {
@@ -40,6 +43,9 @@
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"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"
}
},
@@ -170,6 +176,9 @@
"heater_entity4_id": "4th Heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode"
},
"data_description": {
@@ -179,6 +188,9 @@
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"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"
}
},

View File

@@ -30,6 +30,9 @@
"heater_entity4_id": "4ème radiateur",
"proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent",
"climate_entity2_id": "2ème thermostat sous-jacent",
"climate_entity3_id": "3ème thermostat sous-jacent",
"climate_entity4_id": "4ème thermostat sous-jacent",
"ac_mode": "AC mode ?"
},
"data_description": {
@@ -39,6 +42,9 @@
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent",
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)"
}
},
@@ -170,6 +176,9 @@
"heater_entity4_id": "4ème radiateur",
"proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent",
"climate_entity2_id": "2ème thermostat sous-jacent",
"climate_entity3_id": "3ème thermostat sous-jacent",
"climate_entity4_id": "4ème thermostat sous-jacent",
"ac_mode": "AC mode ?"
},
"data_description": {
@@ -179,6 +188,9 @@
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent",
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)"
}
},

View File

@@ -31,6 +31,9 @@
"heater_entity4_id": "Quarto riscaldatore",
"proportional_function": "Algoritmo",
"climate_entity_id": "Termostato sottostante",
"climate_entity2_id": "Secundo termostato sottostante",
"climate_entity3_id": "Terzo termostato sottostante",
"climate_entity4_id": "Quarto termostato sottostante",
"ac_mode": "AC mode ?"
},
"data_description": {
@@ -40,6 +43,9 @@
"heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)",
"climate_entity_id": "Entity id del termostato sottostante",
"climate_entity2_id": "Entity id del secundo termostato sottostante",
"climate_entity3_id": "Entity id del terzo termostato sottostante",
"climate_entity4_id": "Entity id del quarto termostato sottostante",
"ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?"
}
},
@@ -170,6 +176,9 @@
"heater_entity4_id": "Quarto interruttore riscaldatore",
"proportional_function": "Algoritmo",
"climate_entity_id": "Termostato sottostante",
"climate_entity2_id": "Secundo termostato sottostante",
"climate_entity3_id": "Terzo termostato sottostante",
"climate_entity4_id": "Quarto termostato sottostante",
"ac_mode": "AC mode ?"
},
"data_description": {
@@ -179,6 +188,9 @@
"heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)",
"climate_entity_id": "Entity id del termostato sottostante",
"climate_entity2_id": "Entity id del secundo termostato sottostante",
"climate_entity3_id": "Entity id del terzo termostato sottostante",
"climate_entity4_id": "Entity id del quarto termostato sottostante",
"ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?"
}
},
@@ -301,4 +313,4 @@
}
}
}
}
}