From 208c80752cecc0756417baafcc41b5d3123dde50 Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Sat, 13 Jan 2024 17:46:41 +0000 Subject: [PATCH] Creation of the central boiler config + binary_sensor entity --- .../versatile_thermostat/binary_sensor.py | 77 +++++++++++++++---- .../versatile_thermostat/config_flow.py | 18 ++++- .../versatile_thermostat/config_schema.py | 11 ++- .../versatile_thermostat/const.py | 10 +++ .../versatile_thermostat/strings.json | 16 ++-- .../versatile_thermostat/translations/fr.json | 44 ++++++++--- tests/test_config_flow.py | 7 ++ 7 files changed, 148 insertions(+), 35 deletions(-) diff --git a/custom_components/versatile_thermostat/binary_sensor.py b/custom_components/versatile_thermostat/binary_sensor.py index 3e5cc0a..5b36892 100644 --- a/custom_components/versatile_thermostat/binary_sensor.py +++ b/custom_components/versatile_thermostat/binary_sensor.py @@ -1,10 +1,14 @@ """ Implements the VersatileThermostat binary sensors component """ +# pylint: disable=unused-argument + import logging from homeassistant.core import HomeAssistant, callback, Event from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType + from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorDeviceClass, @@ -15,6 +19,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .commons import VersatileThermostatBaseEntity from .const import ( + DOMAIN, + DEVICE_MANUFACTURER, CONF_NAME, CONF_USE_POWER_FEATURE, CONF_USE_PRESENCE_FEATURE, @@ -42,20 +48,22 @@ async def async_setup_entry( vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG: - return - - entities = [ - SecurityBinarySensor(hass, unique_id, name, entry.data), - WindowByPassBinarySensor(hass, unique_id, name, entry.data), - ] - if entry.data.get(CONF_USE_MOTION_FEATURE): - entities.append(MotionBinarySensor(hass, unique_id, name, entry.data)) - if entry.data.get(CONF_USE_WINDOW_FEATURE): - entities.append(WindowBinarySensor(hass, unique_id, name, entry.data)) - if entry.data.get(CONF_USE_PRESENCE_FEATURE): - entities.append(PresenceBinarySensor(hass, unique_id, name, entry.data)) - if entry.data.get(CONF_USE_POWER_FEATURE): - entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data)) + entities = [ + CentralBoilerBinarySensor(hass, unique_id, name, entry.data), + ] + else: + entities = [ + SecurityBinarySensor(hass, unique_id, name, entry.data), + WindowByPassBinarySensor(hass, unique_id, name, entry.data), + ] + if entry.data.get(CONF_USE_MOTION_FEATURE): + entities.append(MotionBinarySensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_USE_WINDOW_FEATURE): + entities.append(WindowBinarySensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_USE_PRESENCE_FEATURE): + entities.append(PresenceBinarySensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_USE_POWER_FEATURE): + entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data)) async_add_entities(entities, True) @@ -269,7 +277,6 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity): return "mdi:nature-people" -# PR - Adding Window ByPass class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity): """Representation of a BinarySensor which exposes the Window ByPass state""" @@ -307,3 +314,43 @@ class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity return "mdi:window-shutter-cog" else: return "mdi:window-shutter-auto" + + +class CentralBoilerBinarySensor(BinarySensorEntity): + """Representation of a BinarySensor which exposes the Central Boiler state""" + + def __init__( + self, + hass: HomeAssistant, + unique_id, + name, # pylint: disable=unused-argument + entry_infos, + ) -> None: + """Initialize the CentralBoiler Binary sensor""" + self._config_id = unique_id + self._attr_name = "Central boiler" + self._attr_unique_id = "central_boiler_state" + self._attr_is_on = False + self._device_name = entry_infos.get(CONF_NAME) + + @property + def device_info(self) -> DeviceInfo: + """Return the device info.""" + return DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, self._config_id)}, + name=self._device_name, + manufacturer=DEVICE_MANUFACTURER, + model=DOMAIN, + ) + + @property + def device_class(self) -> BinarySensorDeviceClass | None: + return BinarySensorDeviceClass.RUNNING + + @property + def icon(self) -> str | None: + if self._attr_is_on: + return "mdi:water-boiler" + else: + return "mdi:water-boiler-off" diff --git a/custom_components/versatile_thermostat/config_flow.py b/custom_components/versatile_thermostat/config_flow.py index 02c7be9..1a2b6bc 100644 --- a/custom_components/versatile_thermostat/config_flow.py +++ b/custom_components/versatile_thermostat/config_flow.py @@ -263,7 +263,10 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG: self._infos[CONF_NAME] = CENTRAL_CONFIG_NAME schema = STEP_CENTRAL_MAIN_DATA_SCHEMA - next_step = self.async_step_tpi + if user_input and user_input.get(CONF_ADD_CENTRAL_BOILER_CONTROL) is True: + next_step = self.async_step_central_boiler + else: + next_step = self.async_step_tpi elif user_input and user_input.get(CONF_USE_MAIN_CENTRAL_CONFIG) is False: next_step = self.async_step_spec_main schema = STEP_MAIN_DATA_SCHEMA @@ -286,6 +289,19 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): # This will return to async_step_main (to keep the "main" step) return await self.generic_step("main", schema, user_input, next_step) + async def async_step_central_boiler( + self, user_input: dict | None = None + ) -> FlowResult: + """Handle the central boiler flow steps""" + _LOGGER.debug( + "Into ConfigFlow.async_step_central_boiler user_input=%s", user_input + ) + + schema = STEP_CENTRAL_BOILER_SCHEMA + next_step = self.async_step_tpi + + return await self.generic_step("central_boiler", schema, user_input, next_step) + async def async_step_type(self, user_input: dict | None = None) -> FlowResult: """Handle the Type flow steps""" _LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input) diff --git a/custom_components/versatile_thermostat/config_schema.py b/custom_components/versatile_thermostat/config_schema.py index 5971e29..09d352f 100644 --- a/custom_components/versatile_thermostat/config_schema.py +++ b/custom_components/versatile_thermostat/config_schema.py @@ -43,11 +43,12 @@ STEP_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int, vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float), vol.Optional(CONF_USE_CENTRAL_MODE, default=True): cv.boolean, + vol.Required(CONF_USE_MAIN_CENTRAL_CONFIG, default=True): cv.boolean, vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean, vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean, vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean, vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean, - vol.Required(CONF_USE_MAIN_CENTRAL_CONFIG, default=True): cv.boolean, + vol.Required(CONF_USED_BY_CENTRAL_BOILER, default=False): cv.boolean, } ) @@ -58,6 +59,14 @@ STEP_CENTRAL_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name ), vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float), vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float), + vol.Required(CONF_ADD_CENTRAL_BOILER_CONTROL, default=False): cv.boolean, + } +) + +STEP_CENTRAL_BOILER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_CENTRAL_BOILER_ACTIVATION_SRV, default=""): str, + vol.Optional(CONF_CENTRAL_BOILER_DEACTIVATION_SRV, default=""): str, } ) diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index 794d70d..106b62b 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -120,6 +120,12 @@ CONF_USE_ADVANCED_CENTRAL_CONFIG = "use_advanced_central_config" CONF_USE_CENTRAL_MODE = "use_central_mode" +CONF_ADD_CENTRAL_BOILER_CONTROL = "add_central_boiler_control" +CONF_CENTRAL_BOILER_ACTIVATION_SRV = "central_boiler_activation_service" +CONF_CENTRAL_BOILER_DEACTIVATION_SRV = "central_boiler_deactivation_service" + +CONF_USED_BY_CENTRAL_BOILER = "used_by_controls_central_boiler" + DEFAULT_SHORT_EMA_PARAMS = { "max_alpha": 0.5, # In sec @@ -250,6 +256,10 @@ ALL_CONF = ( CONF_USE_PRESENCE_CENTRAL_CONFIG, CONF_USE_ADVANCED_CENTRAL_CONFIG, CONF_USE_CENTRAL_MODE, + CONF_ADD_CENTRAL_BOILER_CONTROL, + CONF_USED_BY_CENTRAL_BOILER, + CONF_CENTRAL_BOILER_ACTIVATION_SRV, + CONF_CENTRAL_BOILER_DEACTIVATION_SRV, ] + CONF_PRESETS_VALUES + CONF_PRESETS_AWAY_VALUES diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index 9bd25ee..a855d76 100644 --- a/custom_components/versatile_thermostat/strings.json +++ b/custom_components/versatile_thermostat/strings.json @@ -24,16 +24,16 @@ "temp_min": "Minimal temperature allowed", "temp_max": "Maximal temperature allowed", "device_power": "Device power", - "use_central_mode": "Enable the control by central entity (need central config)", + "use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.", "use_window_feature": "Use window detection", "use_motion_feature": "Use motion detection", "use_power_feature": "Use power management", "use_presence_feature": "Use presence detection", - "use_main_central_config": "Use central main configuration" + "use_main_central_config": "Use central main configuration. Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm", + "add_central_boiler_control": "Add a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page", + "used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler" }, "data_description": { - "use_central_mode": "Check to enable the control of the VTherm with the select central_mode entities", - "use_main_central_config": "Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected" } }, @@ -256,16 +256,16 @@ "temp_min": "Minimal temperature allowed", "temp_max": "Maximal temperature allowed", "device_power": "Device power", - "use_central_mode": "Enable the control by central entity (need central config)", + "use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.", "use_window_feature": "Use window detection", "use_motion_feature": "Use motion detection", "use_power_feature": "Use power management", "use_presence_feature": "Use presence detection", - "use_main_central_config": "Use central main configuration" + "use_main_central_config": "Use central main configuration. Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm", + "add_central_boiler_control": "Add a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page", + "used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler" }, "data_description": { - "use_central_mode": "Check to enable the control of the VTherm with the select central_mode entities", - "use_main_central_config": "Check to use the central main configuration. Uncheck to use a specific configuration for this VTherm", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected" } }, diff --git a/custom_components/versatile_thermostat/translations/fr.json b/custom_components/versatile_thermostat/translations/fr.json index 3e12736..9590915 100644 --- a/custom_components/versatile_thermostat/translations/fr.json +++ b/custom_components/versatile_thermostat/translations/fr.json @@ -24,17 +24,17 @@ "temp_min": "Température minimale permise", "temp_max": "Température maximale permise", "device_power": "Puissance de l'équipement", - "use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`)", + "use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.", "use_window_feature": "Avec détection des ouvertures", "use_motion_feature": "Avec détection de mouvement", "use_power_feature": "Avec gestion de la puissance", "use_presence_feature": "Avec détection de présence", - "use_main_central_config": "Utiliser la configuration centrale principale" + "use_main_central_config": "Utiliser la configuration centrale. Cochez pour utiliser la configuration centrale. Décochez et saisissez les attributs pour utiliser une configuration spécifique.", + "add_central_boiler_control": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.", + "used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale." }, "data_description": { - "use_central_mode": "Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale", - "external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure.", - "use_main_central_config": "Cochez pour utiliser la configuration centrale principale. Décochez et saisissez les attributs pour utiliser une configuration spécifique principale" + "external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure." } }, "type": { @@ -220,6 +220,18 @@ "security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité", "use_advanced_central_config": "Cochez pour utiliser la configuration centrale avancée. Décochez et saisissez les attributs pour utiliser une configuration spécifique avancée" } + }, + "central_boiler": { + "title": "Contrôle de la chaudière centrale", + "description": "Donnez les services à appeler pour allumer/éteindre la chaudière centrale. Laissez vide, si aucun appel de service ne doit être effectué (dans ce cas, vous devrez gérer vous même l'allumage/extinction de votre chaudière centrale). Le service a appelé doit être formatté comme suit: `entity_id/service_name[/attribut:valeur]` (/attribut:valeur est facultatif)\nPar exemple:\n- pour allumer un switch: `switch.controle_chaudiere/switch.turn_on`\n- pour éteindre un switch: `switch.controle_chaudiere/switch.turn_off`\n- pour programmer la chaudière sur 25° et ainsi forcer son allumage: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- pour envoyer 10° à la chaudière et ainsi forcer son extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`", + "data": { + "central_boiler_activation_service": "Commande pour allumer", + "central_boiler_deactivation_service": "Commande pour éteindre" + }, + "data_description": { + "central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]", + "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]" + } } }, "error": { @@ -256,17 +268,17 @@ "temp_min": "Température minimale permise", "temp_max": "Température maximale permise", "device_power": "Puissance de l'équipement", - "use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`)", + "use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.", "use_window_feature": "Avec détection des ouvertures", "use_motion_feature": "Avec détection de mouvement", "use_power_feature": "Avec gestion de la puissance", "use_presence_feature": "Avec détection de présence", - "use_main_central_config": "Utiliser la configuration centrale" + "use_main_central_config": "Utiliser la configuration centrale. Cochez pour utiliser la configuration centrale. Décochez et saisissez les attributs pour utiliser une configuration spécifique.", + "add_central_boiler_control": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.", + "used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale." }, "data_description": { - "use_central_mode": "Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale", - "use_main_central_config": "Cochez pour utiliser la configuration centrale. Décochez et saisissez les attributs pour utiliser une configuration spécifique", - "external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée" + "external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée." } }, "type": { @@ -446,6 +458,18 @@ "security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité", "use_advanced_central_config": "Cochez pour utiliser la configuration centrale avancée. Décochez et saisissez les attributs pour utiliser une configuration spécifique avancée" } + }, + "central_boiler": { + "title": "Contrôle de la chaudière centrale - {name}", + "description": "Donnez les services à appeler pour allumer/éteindre la chaudière centrale. Laissez vide, si aucun appel de service ne doit être effectué (dans ce cas, vous devrez gérer vous même l'allumage/extinction de votre chaudière centrale). Le service a appelé doit être formatté comme suit: `entity_id/service_name[/attribut:valeur]` (/attribut:valeur est facultatif)\nPar exemple:\n- pour allumer un switch: `switch.controle_chaudiere/switch.turn_on`\n- pour éteindre un switch: `switch.controle_chaudiere/switch.turn_off`\n- pour programmer la chaudière sur 25° et ainsi forcer son allumage: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- pour envoyer 10° à la chaudière et ainsi forcer son extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`", + "data": { + "central_boiler_activation_service": "Commande pour allumer", + "central_boiler_deactivation_service": "Commande pour éteindre" + }, + "data_description": { + "central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]", + "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]" + } } }, "error": { diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index d04f613..509d868 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -143,6 +143,8 @@ async def test_user_config_flow_over_switch( CONF_USE_POWER_CENTRAL_CONFIG: True, CONF_USE_PRESENCE_CENTRAL_CONFIG: True, CONF_USE_ADVANCED_CENTRAL_CONFIG: True, + CONF_USE_CENTRAL_MODE: True, + CONF_USED_BY_CENTRAL_BOILER: False, } ) assert result["result"] @@ -239,6 +241,8 @@ async def test_user_config_flow_over_climate( CONF_USE_POWER_CENTRAL_CONFIG: False, CONF_USE_PRESENCE_CENTRAL_CONFIG: False, CONF_USE_ADVANCED_CENTRAL_CONFIG: False, + CONF_ADD_CENTRAL_BOILER_CONTROL: False, + CONF_USED_BY_CENTRAL_BOILER: False, } assert result["result"] assert result["result"].domain == DOMAIN @@ -287,6 +291,7 @@ async def test_user_config_flow_window_auto_ok( CONF_USE_POWER_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False, CONF_USE_MAIN_CENTRAL_CONFIG: True, + CONF_USED_BY_CENTRAL_BOILER: True, }, ) @@ -373,6 +378,7 @@ async def test_user_config_flow_window_auto_ok( CONF_USE_POWER_CENTRAL_CONFIG: False, CONF_USE_PRESENCE_CENTRAL_CONFIG: False, CONF_USE_ADVANCED_CENTRAL_CONFIG: True, + CONF_USED_BY_CENTRAL_BOILER: True, } assert result["result"] assert result["result"].domain == DOMAIN @@ -512,6 +518,7 @@ async def test_user_config_flow_over_4_switches( CONF_USE_PRESENCE_FEATURE: False, CONF_USE_MAIN_CENTRAL_CONFIG: True, CONF_USE_CENTRAL_MODE: False, + CONF_USED_BY_CENTRAL_BOILER: False, } TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name