From c12a91a5ff112131dec4ba0cd450264a57d0b53c Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Sun, 21 Jan 2024 00:31:16 +0100 Subject: [PATCH] Feature 234 add central boiler helper (#352) * Creation of the central boiler config + binary_sensor entity * Fonctional before testu. Miss the service call * Full featured but without testu * Documentation and release. * Add events in README * FIX #341 - when window state change, open_valve_percent should be resend * Issue #343 - disable safety mode for outdoor thermometer * Issue #255 - Specify window action on window open detection * Add en and string translation * central boiler - add entites to fine tune the boiler start * With testu ok * Add testus for valve and climate * Add testus in pipelines * With pip 3 * With more pytest options * Ass coverage tests * Add coverage report in github * Release 5.3.0 --------- Co-authored-by: Jean-Marc Collin --- .devcontainer/configuration.yaml | 11 + .devcontainer/devcontainer.json | 3 +- .github/workflows/testus.yaml | 50 ++ .gitignore | 3 + .vscode/tasks.json | 6 + README-fr.md | 130 ++- README.md | 129 ++- container | 9 + .../versatile_thermostat/__init__.py | 16 + .../versatile_thermostat/base_thermostat.py | 179 +++- .../versatile_thermostat/binary_sensor.py | 217 ++++- .../versatile_thermostat/commons.py | 147 ++- .../versatile_thermostat/config_flow.py | 37 +- .../versatile_thermostat/config_schema.py | 61 +- .../versatile_thermostat/const.py | 54 +- .../versatile_thermostat/manifest.json | 4 +- .../versatile_thermostat/number.py | 117 +++ .../versatile_thermostat/select.py | 2 +- .../versatile_thermostat/sensor.py | 196 +++- .../versatile_thermostat/strings.json | 47 +- .../thermostat_climate.py | 6 +- .../versatile_thermostat/thermostat_switch.py | 1 + .../versatile_thermostat/thermostat_valve.py | 8 +- .../versatile_thermostat/translations/en.json | 47 +- .../versatile_thermostat/translations/fr.json | 75 +- .../versatile_thermostat/underlyings.py | 18 +- .../versatile_thermostat/vtherm_api.py | 70 +- images/config-central-boiler-1.png | Bin 0 -> 26165 bytes images/config-central-boiler-2.png | Bin 0 -> 87269 bytes images/dev-tools-turnon-boiler-1.png | Bin 0 -> 48296 bytes images/dev-tools-turnon-boiler-2.png | Bin 0 -> 31971 bytes images/entitites-central-boiler.png | Bin 0 -> 24887 bytes requirements_test.txt | 2 + tests/commons.py | 129 ++- tests/conftest.py | 18 +- tests/const.py | 1 + tests/test_central_boiler.py | 835 ++++++++++++++++++ tests/test_central_config.py | 34 +- tests/test_config_flow.py | 6 + tests/test_valve.py | 5 +- tests/test_window.py | 699 ++++++++++++++- 41 files changed, 3165 insertions(+), 207 deletions(-) create mode 100644 .github/workflows/testus.yaml create mode 100644 custom_components/versatile_thermostat/number.py create mode 100644 images/config-central-boiler-1.png create mode 100644 images/config-central-boiler-2.png create mode 100644 images/dev-tools-turnon-boiler-1.png create mode 100644 images/dev-tools-turnon-boiler-2.png create mode 100644 images/entitites-central-boiler.png create mode 100644 tests/test_central_boiler.py diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index 0583d22..724f8cb 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -28,6 +28,8 @@ versatile_thermostat: max_alpha: 0.6 halflife_sec: 301 precision: 3 + safety_mode: + check_outdoor_sensor: false input_number: fake_temperature_sensor1: @@ -66,6 +68,13 @@ input_number: max: 90 icon: mdi:pipe-valve unit_of_measurement: percentage + fake_boiler_temperature: + name: Central thermostat temp + min: 0 + max: 30 + icon: mdi:thermostat + unit_of_measurement: °C + mode: box input_boolean: # input_boolean to simulate the windows entity. Only for development environment. @@ -161,6 +170,7 @@ climate: target_sensor: input_number.fake_temperature_sensor1 recorder: + commit_interval: 1 include: domains: - input_boolean @@ -168,6 +178,7 @@ recorder: - switch - climate - sensor + - binary_sensor template: - binary_sensor: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8e13a01..f7884d7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -30,7 +30,8 @@ "waderyan.gitblame", "keesschollaart.vscode-home-assistant", "vscode.markdown-math", - "yzhang.markdown-all-in-one" + "yzhang.markdown-all-in-one", + "github.vscode-github-actions" ], "settings": { "files.eol": "\n", diff --git a/.github/workflows/testus.yaml b/.github/workflows/testus.yaml new file mode 100644 index 0000000..35dc86f --- /dev/null +++ b/.github/workflows/testus.yaml @@ -0,0 +1,50 @@ +name: Run Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + testu: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip3 install -r requirements_test.txt + + - name: Run Tests + run: | + pytest \ + -qq \ + --timeout=9 \ + --durations=10 \ + -n auto \ + -o console_output_style=count \ + -p no:sugar \ + tests + + - name: Coverage + run: | + coverage run -m pytest tests/ + coverage report + + - name: Generate HTML Coverage Report + run: coverage html + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./htmlcov diff --git a/.gitignore b/.gitignore index e7e27b9..99ed805 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,6 @@ __pycache__ config/** custom_components/hacs custom_components/localtuya + +.coverage +htmlcov \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1e3f53a..f742e46 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,12 @@ "command": "./container restart", "problemMatcher": [] }, + { + "label": "Start coverage", + "type": "shell", + "command": "./container coverage", + "problemMatcher": [] + }, { "label": "Home Assistant translations update", "type": "shell", diff --git a/README-fr.md b/README-fr.md index cffc9e2..c937e9e 100644 --- a/README-fr.md +++ b/README-fr.md @@ -36,6 +36,11 @@ - [Configurer la présence ou l'occupation](#configurer-la-présence-ou-loccupation) - [Configuration avancée](#configuration-avancée) - [Le contrôle centralisé](#le-contrôle-centralisé) + - [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale) + - [Configuration](#configuration-1) + - [Comment trouver le bon service ?](#comment-trouver-le-bon-service-) + - [Les évènements](#les-évènements) + - [Avertissement](#avertissement) - [Synthèse des paramètres](#synthèse-des-paramètres) - [Exemples de réglage](#exemples-de-réglage) - [Chauffage électrique](#chauffage-électrique) @@ -79,14 +84,15 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une > ![Nouveau](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true) _*Nouveautés*_ +> * **Release 5.3** : Ajout d'une fonction de pilotage d'une chaudière centrale [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - plus d'infos ici: [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale). Ajout de la possibilité de désactiver le mode sécurité pour le thermomètre extérieur [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343) > * **Release 5.2** : Ajout d'un `central_mode` permettant de piloter tous les VTherms de façon centralisée [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158). > * **Release 5.1** : Limitation des valeurs envoyées aux valves et au température envoyées au climate sous-jacent. > * **Release 5.0** : Ajout d'une configuration centrale permettant de mettre en commun les attributs qui peuvent l'être [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239). > * **Release 4.3** : Ajout d'un mode auto-fan pour le type `over_climate` permettant d'activer la ventilation si l'écart de température est important [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223). -> * **Release 4.2** : Le calcul de la pente de la courbe de température se fait maintenant en °/heure et non plus en °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction de la détection automatique des ouvertures par l'ajout d'un lissage de la courbe de température .
Autres versions +> * **Release 4.2** : Le calcul de la pente de la courbe de température se fait maintenant en °/heure et non plus en °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction de la détection automatique des ouvertures par l'ajout d'un lissage de la courbe de température . > * **Release 4.1** : Ajout d'un mode de régulation **Expert** dans lequel l'utilisateur peut spécifier ses propres paramètres d'auto-régulation au lieu d'utiliser les pre-programmés [#194](https://github.com/jmcollin78/versatile_thermostat/issues/194). > * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144) > * **Release 3.8**: Ajout d'une **fonction d'auto-régulation** pour les thermostats `over climate` dont la régulation est faite par le climate sous-jacent. Cf. [L'auto-régulation](#lauto-régulation) et [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Ajout de la **possibilité d'inverser la commande** pour un thermostat `over switch` pour adresser les installations avec fil pilote et diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124). @@ -159,6 +165,7 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant - Des **services pour interagir avec le thermostat** à partir d'autres intégrations : vous pouvez forcer la présence / la non-présence à l'aide d'un service, et vous pouvez modifier dynamiquement la température des préréglages et changer les paramètres de sécurité. - Ajouter des capteurs pour voir les états internes du thermostat, - Contrôle centralisé de tous les Versatile Thermostat pour les stopper tous, les passer tous en hors-gel, les forcer en mode Chauffage (l'hiver), les forcer en mode Climatisation (l'été). +- Contrôle d'une chaudière centrale et des VTherm qui doivent contrôler cette chaudière. # Comment installer cet incroyable Thermostat Versatile ? @@ -526,9 +533,18 @@ Mettre ce paramètre à ``0.00`` déclenchera le préréglage sécurité quelque Le quatrième param§tre (``security_default_on_percent``) est la valeur de ``on_percent`` qui sera utilisée lorsque le thermostat passe en mode ``security``. Si vous mettez ``0`` alors le thermostat sera coupé lorsqu'il passe en mode ``security``, mettre 0,2% par exemple permet de garder un peu de chauffage (20% dans ce cas), même en mode ``security``. Ca évite de retrouver son logement totalement gelé lors d'une panne de thermomètre. +Depuis la version 5.3 il est possible de désactiver la mise en sécurité suite à une absence de données du thermomètre extérieure. En effet, celui-ci ayant la plupart du temps un impact faible sur la régulation (dépendant de votre paramètrage), il est possible qu'il soit absent sans mettre en danger le logement. Pour cela, il faut ajouter les lignes suivantes dans votre `configuration.yaml` : +``` +versatile_thermostat: +... + safety_mode: + check_outdoor_sensor: false +``` +Par défaut, le thermomètre extérieur peut déclencher une mise en sécurité si il n'envoit plus de valeur. + Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglage communs -> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ +> ![Astuce](/images/tips.png?raw=true) _*Notes*_ > 1. Lorsque le capteur de température viendra à la vie et renverra les températures, le préréglage sera restauré à sa valeur précédente, > 2. Attention, deux températures sont nécessaires : la température interne et la température externe et chacune doit donner la température, sinon le thermostat sera en préréglage "security", > 3. Un service est disponible qui permet de régler les 3 paramètres de sécurité. Ca peut servir à adapter la fonction de sécurité à votre usage, @@ -550,6 +566,106 @@ Exemple de rendu : ![central_mode](/images/central_mode.png?raw=true) +## Le contrôle d'une chaudière centrale +Depuis la release 5.3, vous avez la possibilité de contrôler une chaudière centralisée. A partir du moment où il est possible de déclencher ou stopper cette chaudière depuis Home Assistant, alors Versatile Thermostat va pouvoir la commander directement. + +Le principe mis en place est globalement le suivant : +1. une nouvelle entité de type `binary_sensor` et nommée par défaut `binary_sensor.central_boiler` est ajoutée, +2. dans la configuration des VTherms vous indiquez si le VTherm doit contrôler la chaudière. En effet, dans une installation hétérogène, certains VTherm doivent commander la chaudière et d'autres non. Vous devez donc indiquer dans chaque configuration de VTherm si il contrôle la chaudière ou pas, +3. le `binary_sensor.central_boiler` écoute les changements d'états des équipements des VTherm marqués comme contrôlant la chaudière, +4. dès que le nombre d'équipements pilotés par le VTherm demandant du chauffage (ie son `hvac_action` passe à `Heating`) dépasse un seuil paramétrable, alors le `binary_sensor.central_boiler` passe à `on` et **si un service d'activation a été configuré, alors ce service est appelé**, +5. si le nombre d'équipements nécessitant du chauffage repasse en dessous du seuil, alors le `binary_sensor.central_boiler` passe à `off` et si **un service de désactivation a été configuré, alors ce service est appelé**, +6. vous avez accès à deux entités : + - une de type `number` nommé par défaut `number.boiler_activation_threshold`, donne le seuil de déclenchement. Ce seuil est en nombre d'équipements (radiateurs) qui demande du chauffage. + - une de type `sensor` nommé par défaut `sensor.nb_device_active_for_boiler`, donne le nombre d'équipements qui demande du chauffage. Par exemple, un VTherm ayant 4 vannes dont 3 demandes du chauffage fera passé ce capteur à 3. Seuls les équipements des VTherms qui sont marqués pour contrôler la chaudière centrale sont comptabilisés. + +Vous avez donc en permanence, les informations qui permettent de piloter et régler le déclenchement de la chaudière. + +Toutes ces entités sont rattachés au service de configuration centrale : +![Les entités pilotant la chaudière](/images/entitites-central-boiler.png?raw=true) + +### Configuration +Pour configurer cette fonction, vous devez avoir une configuration centralisée (cf. [Configuration](#configuration)) et cochez la case 'Ajouter une chuadière centrale' : + +![Ajout d'une chaudière centrale](/images/config-central-boiler-1.png?raw=true) + +Sur la page suivante vous pouvez donner la configuration des services à appeler lors de l'allumage / extinction de la chaudière : + +![Ajout d'une chaudière centrale](/images/config-central-boiler-2.png?raw=true) + +Les services se configurent comme indiqués dans la page : +1. le format général est `entity_id/service_id[/attribut:valeur]` (où `/attribut:valeur` est facultatif), +2. `entity_id` est le nom de l'entité qui commande la chaudière sous la forme `domain.entity_name`. Par exemple: `switch.chaudiere` pour les chaudière commandée par un switch ou `climate.chaudière` pour une chaudière commandée par un thermostat ou tout autre entité qui permet le contrôle de la chaudière (il n'y a pas de limitation). On peut aussi commuter des entrées (`helpers`) comme des `input_boolean` ou `input_number`. +3. `service_id` est le nom du service à appeler sous la forme `domain.service_name`. Par exemple: `switch.turn_on`, `switch.turn_off`, `climate.set_temperature`, `climate.set_hvac_mode` sont des exemples valides. +4. pour certain service vous aurez besoin d'un paramètre. Cela peut être le 'Mode CVC' `climate.set_hvac_mode` ou la température cible pour `climate.set_temperature`. Ce paramètre doit être configuré sous la forme `attribut:valeur` en fin de chaine. + +Exemples (à ajuster à votre cas) : +- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:heat` : pour allumer le thermostat de la chaudière en mode chauffage, +- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:off` : pour stopper le thermostat de la chaudière, +- `switch.pompe_chaudiere/switch.turn_on` : pour allumer le swicth qui alimente la pompe de la chaudière, +- `switch.pompe_chaudiere/switch.turn_off` : pour allumer le swicth qui alimente la pompe de la chaudière, +- ... + +### Comment trouver le bon service ? +Pour trouver le services a utiliser, le mieux est d'aller dans "Outils de développement / Services", chercher le service a appelé, l'entité à commander et l'éventuel paramètre à donner. +Cliquez sur 'Appeler le service'. Si votre chaudière s'allume vous avez la bonne configuration. Passez alors en mode Yaml et recopiez les paramètres. + +Exemple: + +Sous "Outils de développement / Service" : + +![Configuration du service](/images/dev-tools-turnon-boiler-1.png?raw=true) + +En mode yaml : + +![Configuration du service](/images/dev-tools-turnon-boiler-2.png?raw=true) + +Le service à configurer est alors le suivant: `climate.empty_thermostast/climate.set_hvac_mode/hvac_mode:heat` (notez la suppression du blanc dans `hvac_mode:heat`) + +Faite alors de même pour le service d'extinction et vous êtes parés. + +### Les évènements + +A chaque allumage ou extinction réussie de la chaudière un évènement est envoyé par Versatile Thermostat. Il peut avantageusement être capté par une automatisation, par exemple pour notifier un changement. +Les évènements ressemblent à ça : + +Un évènement d'allumage : +``` +event_type: versatile_thermostat_central_boiler_event +data: + central_boiler: true + entity_id: binary_sensor.central_boiler + name: Central boiler + state_attributes: null +origin: LOCAL +time_fired: "2024-01-14T11:33:52.342026+00:00" +context: + id: 01HM3VZRJP3WYYWPNSDAFARW1T + parent_id: null + user_id: null +``` + +Un évènement d'extinction : +``` +event_type: versatile_thermostat_central_boiler_event +data: + central_boiler: false + entity_id: binary_sensor.central_boiler + name: Central boiler + state_attributes: null +origin: LOCAL +time_fired: "2024-01-14T11:43:52.342026+00:00" +context: + id: 01HM3VZRJP3WYYWPNSDAFBRW1T + parent_id: null + user_id: null +``` + +### Avertissement + +> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ +> Le contrôle par du logiciel ou du matériel de type domotique d'une chaudière centrale peut induire des risques pour son bon fonctionnement. Assurez-vous avant d'utiliser ces fonctions, que votre chaudière possède bien des fonctions de sécurité et que celles-ci fonctionnent. Allumer une chaudière si tous les robinets sont fermés peut générer de la sur-pression par exemple. + ## Synthèse des paramètres | Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "configuration centrale" | @@ -618,7 +734,11 @@ Exemple de rendu : | ``auto_regulation_dtemp`` | La seuil d'auto-régulation | - | X | - | - | | ``auto_regulation_period_min`` | La période minimale d'auto-régulation | - | X | - | - | | ``inverse_switch_command`` | Inverse la commande du switch (pour switch avec fil pilote) | X | - | - | - | -| ``auto_fan_mode` | Mode de ventilation automatique | - | X | - | - | +| ``auto_fan_mode`` | Mode de ventilation automatique | - | X | - | - | +| ``add_central_boiler_control`` | Ajout du controle d'une chaudière centrale | - | - | - | X | +| ``central_boiler_activation_service`` | Service d'activation de la chaudière | - | - | - | X | +| ``central_boiler_deactivation_service`` | Service de desactivation de la chaudière | - | - | - | X | +| ``used_by_controls_central_boiler`` | Indique si le VTherm contrôle la chaudière centrale | X | X | X | - | # Exemples de réglage @@ -814,6 +934,7 @@ Les évènements notifiés sont les suivants: - ``versatile_thermostat_temperature_event`` : une ou les deux mesures de température d'un thermostat n'ont pas été mis à jour depuis plus de `security_delay_min`` minutes - ``versatile_thermostat_hvac_mode_event`` : le thermostat est allumé ou éteint. Cet évènement est aussi diffusé au démarrage du thermostat - ``versatile_thermostat_preset_event`` : un nouveau preset est sélectionné sur le thermostat. Cet évènement est aussi diffusé au démarrage du thermostat +- ``versatile_thermostat_central_boiler_event`` : un évènement indiquant un changement dans l'état de la chaudière. Si vous avez bien suivi, lorsqu'un thermostat passe en mode sécurité, 3 évènements sont déclenchés : 1. ``versatile_thermostat_temperature_event`` pour indiquer qu'un thermomètre ne répond plus, @@ -873,6 +994,7 @@ Les attributs personnalisés sont les suivants : | ``is_inversed`` | True si la commande est inversée (fil pilote avec diode) | | ``is_controlled_by_central_mode`` | True si le VTherm peut être controlé de façon centrale | | ``last_central_mode`` | Le dernier mode central utilisé (None si le VTherm n'est pas controlé en central) | +| ``is_used_by_central_boiler`` | Indique si le VTherm peut contrôler la chaudière centrale | # Quelques résultats @@ -1206,7 +1328,7 @@ Avec un VTherm de type `over_switch` ou `over_valve`, ce défaut montre juste qu ### Type `over_climate` Avec un VTherm de type `over_climate`, la régulation est faite par le `climate` sous-jacent directement et VTherm se contente de lui transmettre les consignes. Donc si le radiateur chauffe alors que la température de consigne est dépassée, c'est certainement que sa mesure de température interne est biaisée. Ca arrive très souvent avec les TRV et les clims réversibles qui ont un capteur de température interne, soit trop près de l'élément de chauffe (donc trop froid l'hiver). -Exemple de discussion autour de ces sujets: [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278) +Exemple de discussion autour de ces sujets: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278) Pour s'en sortir, VTherm est équipé d'une fonction nommée auto-régulation qui permet d'adapter la consigne envoyée au sous-jacent jusqu'à ce que la consigne soit respectée. Cette fonction permet de compenser le biais de mesure des thermomètres internes. Si le biais est important la régulation doit être importante. Voir [L'auto-régulation](#lauto-régulation) pour configurer l'auto-régulation. diff --git a/README.md b/README.md index bf38c4d..2250230 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,11 @@ - [Configure presence or occupancy](#configure-presence-or-occupancy) - [Advanced configuration](#advanced-configuration) - [Centralized control](#centralized-control) + - [Control of a central boiler](#control-of-a-central-boiler) + - [Setup](#setup) + - [How to find the right service?](#how-to-find-the-right-service) + - [The events](#the-events) + - [Warning](#warning) - [Parameters synthesis](#parameters-synthesis) - [Examples tuning](#examples-tuning) - [Electrical heater](#electrical-heater) @@ -79,14 +84,15 @@ This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features. >![New](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true) _*News*_ +> * **Release 5.3**: Added a central boiler control function [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - more information here: [Controlling a central boiler](#controlling-a-central-boiler). Added the ability to disable security mode for outdoor thermometer [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343) > * **Release 5.2**: Added a `central_mode` allowing all VTherms to be controlled centrally [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158). > * **Release 5.1**: Limitation of the values sent to the valves and the temperature sent to the underlying climate. > * **Release 5.0**: Added a central configuration allowing the sharing of attributes that can be shared [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239). > * **Release 4.3**: Added an auto-fan mode for the `over_climate` type allowing ventilation to be activated if the temperature difference is significant [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223). -> * **Release 4.2**: The calculation of the slope of the temperature curve is now done in °/hour and no longer in °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction of automatic detection of openings by adding smoothing of the temperature curve.
Others releases +> * **Release 4.2**: The calculation of the slope of the temperature curve is now done in °/hour and no longer in °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction of automatic detection of openings by adding smoothing of the temperature curve. > * **Release 4.1**: Added an **Expert** regulation mode in which the user can specify their own auto-regulation parameters instead of using the pre-programmed ones [#194]( https://github.com/jmcollin78/versatile_thermostat/issues/194). > * **Release 4.0**: Added the support of **Versatile Thermostat UI Card**. See [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Added a **Slow** regulation mode for slow latency heating devices [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Change the way **the power is calculated** in case of VTherm with multi-underlying equipements [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Added the support of AC and Heat for VTherm over switch alse [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144) > * **Release 3.8**: Added a **self-regulation function** for `over climate` thermostats whose regulation is done by the underlying climate. See [Self-regulation](#self-regulation) and [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Added the possibility of **inverting the command** for an `over switch` thermostat to address installations with pilot wire and diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124). @@ -159,6 +165,7 @@ This component named __Versatile thermostat__ manage the following use cases : - Add **services to interact with the thermostat** from others integration: you can force the presence / un-presence using a service, and you can dynamically change the temperature of the presets and change dynamically the safety parameters. - Add sensors to see the internal states of the thermostat, - Centralized control of all Versatile Thermostats to stop them all, switch them all to frost protection, force them into Heating mode (winter), force them into Cooling mode (summer). +- Control of a central boiler and the VTherms which must control this boiler. # How to install this incredible Versatile Thermostat ? @@ -512,6 +519,15 @@ Setting this parameter to ``0.00`` will trigger the safety preset regardless of The fourth parameter (``security_default_on_percent``) is the ``on_percent`` value that will be used when the thermostat enters ``safety`` mode. If you put ``0`` then the thermostat will be cut off when it goes into ``safety`` mode, putting 0.2% for example allows you to keep a little heating (20% in this case), even in mode ``safety``. It avoids finding your home totally frozen during a thermometer failure. +Since version 5.3 it is possible to deactivate the safety device following a lack of data from the outdoor thermometer. Indeed, this most of the time having a low impact on regulation (depending on your settings), it is possible that it is absent without endangering the home. To do this, you must add the following lines to your `configuration.yaml`: +``` +versatile_thermostat: +... + safety_mode: + check_outdoor_sensor: false +``` +By default, the outdoor thermometer can trigger a trip if it no longer sends a value. + See [example tuning](#examples-tuning) for common tuning examples >![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ @@ -536,6 +552,106 @@ Example rendering: ![central_mode](/images/central_mode.png?raw=true) +## Control of a central boiler +Since release 5.3, you have the possibility of controlling a centralized boiler. From the moment it is possible to start or stop this boiler from Home Assistant, then Versatile Thermostat will be able to control it directly. + +The principle put in place is generally as follows: +1. a new entity of type `binary_sensor` and named by default `binary_sensor.central_boiler` is added, +2. in the VTherms configuration you indicate whether the VTherm should control the boiler. Indeed, in a heterogeneous installation, some VTherm must control the boiler and others not. You must therefore indicate in each VTherm configuration whether it controls the boiler or not, +3. the `binary_sensor.central_boiler` listens for changes in state of VTherm equipment marked as controlling the boiler, +4. as soon as the number of devices controlled by the VTherm requesting heating (ie its `hvac_action` goes to `Heating`) exceeds a configurable threshold, then the `binary_sensor.central_boiler` goes to `on` and **if a activation service has been configured, then this service is called**, +5. if the number of devices requiring heating falls below the threshold again, then the `binary_sensor.central_boiler` goes to `off` and if **a deactivation service has been configured, then this service is called**, +6. you have access to two entities: + - one of type `number` named by default `number.boiler_activation_threshold`, gives the trigger threshold. This threshold is in number of equipment (radiators) which requires heating. + - one of type `sensor` named by default `sensor.nb_device_active_for_boiler`, gives the number of devices requiring heating. For example, a VTherm having 4 valves including 3 heating requests will increase this sensor to 3. Only VTherm equipment that is marked to control the central boiler is counted. + +You therefore always have the information which allows you to control and adjust the activation of the boiler. + +All these entities are attached to the central configuration service: +![The entities controlling the boiler](/images/entitites-central-boiler.png?raw=true) + +### Setup +To configure this function, you must have a centralized configuration (see [Configuration](#configuration)) and check the 'Add a central boiler' box: + +![Adding a central boiler](/images/config-central-boiler-1.png?raw=true) + +On the following page you can configure the services to be called when switching the boiler on/off: + +![Adding a central boiler](/images/config-central-boiler-2.png?raw=true) + +The services are configured as indicated on the page: +1. the general format is `entity_id/service_id[/attribute:value]` (where `/attribute:value` is optional), +2. `entity_id` is the name of the entity that controls the boiler in the form `domain.entity_name`. For example: `switch.boiler` for boilers controlled by a switch or `climate.boiler` for a boiler controlled by a thermostat or any other entity which allows control of the boiler (there is no limitation). We can also switch inputs (`helpers`) like `input_boolean` or `input_number`. +3. `service_id` is the name of the service to call in the form `domain.service_name`. For example: `switch.turn_on`, `switch.turn_off`, `climate.set_temperature`, `climate.set_hvac_mode` are valid examples. +4. For some service you will need a parameter. This can be the 'HVAC Mode' `climate.set_hvac_mode` or the target temperature for `climate.set_temperature`. This parameter must be configured in the form `attribute:value` at the end of the string. + +Examples (to be adjusted to your case): +- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:heat`: to turn on the boiler thermostat in heating mode, +- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:off`: to stop the boiler thermostat, +- `switch.pompe_chaudiere/switch.turn_on`: to turn on the switch which powers the boiler pump, +- `switch.pompe_chaudiere/switch.turn_off`: to turn on the switch which powers the boiler pump, +- ... + +### How to find the right service? +To find the service to use, the best is to go to "Development tools / Services", look for the service called, the entity to order and the possible parameter to give. +Click on 'Call Service'. If your boiler lights up you have the correct configuration. Then switch to Yaml mode and copy the parameters. + +Example: + +Under "Development Tools / Service": + +![Service configuration](/images/dev-tools-turnon-boiler-1.png?raw=true) + +In yaml mode: + +![Service configuration](/images/dev-tools-turnon-boiler-2.png?raw=true) + +The service to configure is then the following: `climate.empty_thermostast/climate.set_hvac_mode/hvac_mode:heat` (note the removal of the blank in `hvac_mode:heat`) + +Then do the same for the extinguishing service and you are all set. + +### The events + +Each time the boiler is successfully switched on or off, an event is sent by Versatile Thermostat. It can advantageously be captured by automation, for example to notify a change. +The events look like this: + +An ignition event: +``` +event_type: versatile_thermostat_central_boiler_event +data: + central_boiler: true + entity_id: binary_sensor.central_boiler + name: Central boiler + state_attributes: null +origin: LOCAL +time_fired: "2024-01-14T11:33:52.342026+00:00" +context: + id: 01HM3VZRJP3WYYWPNSDAFARW1T + parent_id: null + user_id: null +``` + +An extinction event: +``` +event_type: versatile_thermostat_central_boiler_event +data: + central_boiler: false + entity_id: binary_sensor.central_boiler + name: Central boiler + state_attributes: null +origin: LOCAL +time_fired: "2024-01-14T11:43:52.342026+00:00" +context: + id: 01HM3VZRJP3WYYWPNSDAFBRW1T + parent_id: null + user_id: null +``` + +### Warning + +> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ +> Controlling a central boiler using software or hardware such as home automation can pose risks to its proper functioning. Before using these functions, make sure that your boiler has safety functions and that they are working. Turning on a boiler if all the taps are closed can generate excess pressure, for example. + ## Parameters synthesis | Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "central configuration" | @@ -605,7 +721,12 @@ Example rendering: | ``auto_regulation_dtemp`` | La seuil d'auto-régulation | - | X | - | - | | ``auto_regulation_period_min`` | La période minimale d'auto-régulation | - | X | - | - | | ``inverse_switch_command`` | Inverse the switch command (for pilot wire switch) | X | - | - | - | -| ``auto_fan_mode` | Auto fan mode | - | X | - | - | +| ``auto_fan_mode`` | Auto fan mode | - | X | - | - | +| ``add_central_boiler_control`` | Add the control of a central boiler | - | - | - | X | +| ``central_boiler_activation_service`` | Activation service of the boiler | - | - | - | X | +| ``central_boiler_deactivation_service`` | Deactivaiton service of the boiler | - | - | - | X | +| ``used_by_controls_central_boiler`` | Indicate if the VTherm control the central boiler | X | X | X | - | + # Examples tuning @@ -799,6 +920,7 @@ The notified events are as follows: - ``versatile_thermostat_temperature_event``: one or both temperature measurements of a thermostat have not been updated for more than ``security_delay_min`` minutes - ``versatile_thermostat_hvac_mode_event``: the thermostat is on or off. This event is also broadcast when the thermostat starts up - ``versatile_thermostat_preset_event``: a new preset is selected on the thermostat. This event is also broadcast when the thermostat starts up +- ``versatile_thermostat_central_boiler_event``: an event indicating a change in the state of the central boiler. If you have followed correctly, when a thermostat goes into safety mode, 3 events are triggered: 1. ``versatile_thermostat_temperature_event`` to indicate that a thermometer has become unresponsive, @@ -858,6 +980,7 @@ Custom attributes are the following: | ``is_inversed`` | True if the command is inversed (pilot wire with diode) | | ``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 | # Some results @@ -1190,7 +1313,7 @@ With a VTherm of type `over_switch` or `over_valve`, this fault just shows that ### Type `over_climate` With an `over_climate` type VTherm, the regulation is done by the underlying `climate` directly and VTherm simply transmits the instructions to it. So if the radiator heats up when the set temperature is exceeded, it is certainly because its internal temperature measurement is biased. This happens very often with TRVs and reversible air conditioning units which have an internal temperature sensor, or too close to the heating element (therefore too cold in winter). -Example of discussion around these topics: [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312 ), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278) +Example of discussion around these topics: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312 ), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278) To get around this, VTherm is equipped with a function called self-regulation which allows the instruction sent to the underlying to be adapted until the target temperature is respected. This function compensates for the measurement bias of internal thermometers. If the bias is important the regulation must be important. See [Self-regulation](#self-regulation) to configure self-regulation. diff --git a/container b/container index b602188..35b64b7 100755 --- a/container +++ b/container @@ -43,4 +43,13 @@ case $1 in pwd ./scripts/starts_ha.sh ;; + coverage) + rm -rf htmlcov/* + echo "Starting coverage tests" + coverage run -m pytest tests/ + echo "Starting coverage report" + coverage report + echo "Starting coverage html" + coverage html + ;; esac diff --git a/custom_components/versatile_thermostat/__init__.py b/custom_components/versatile_thermostat/__init__.py index e76483e..77d6c68 100644 --- a/custom_components/versatile_thermostat/__init__.py +++ b/custom_components/versatile_thermostat/__init__.py @@ -24,6 +24,7 @@ from .const import ( CONF_AUTO_REGULATION_SLOW, CONF_AUTO_REGULATION_EXPERT, CONF_SHORT_EMA_PARAMS, + CONF_SAFETY_MODE, CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_TYPE, ) @@ -47,12 +48,17 @@ EMA_PARAM_SCHEMA = { vol.Required("precision"): cv.positive_int, } +SAFETY_MODE_PARAM_SCHEMA = { + vol.Required("check_outdoor_sensor"): bool, +} + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { CONF_AUTO_REGULATION_EXPERT: vol.Schema(SELF_REGULATION_PARAM_SCHEMA), CONF_SHORT_EMA_PARAMS: vol.Schema(EMA_PARAM_SCHEMA), + CONF_SAFETY_MODE: vol.Schema(SAFETY_MODE_PARAM_SCHEMA), } ), }, @@ -105,6 +111,9 @@ async def reload_all_vtherm(hass): ] await asyncio.gather(*reload_tasks) + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass) + if api: + await api.reload_central_boiler_entities_list() async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -124,6 +133,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + await api.reload_central_boiler_entities_list() + return True @@ -133,6 +144,10 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: await reload_all_vtherm(hass) else: await hass.config_entries.async_reload(entry.entry_id) + # Reload the central boiler list of entities + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass) + if api is not None: + await api.reload_central_boiler_entities_list() async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -142,6 +157,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): if api: api.remove_entry(entry) + await api.reload_central_boiler_entities_list() return unload_ok diff --git a/custom_components/versatile_thermostat/base_thermostat.py b/custom_components/versatile_thermostat/base_thermostat.py index 753f986..55f9609 100644 --- a/custom_components/versatile_thermostat/base_thermostat.py +++ b/custom_components/versatile_thermostat/base_thermostat.py @@ -121,6 +121,7 @@ from .const import ( CENTRAL_MODE_HEAT_ONLY, CENTRAL_MODE_COOL_ONLY, CENTRAL_MODE_FROST_PROTECTION, + send_vtherm_event, ) from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import @@ -196,6 +197,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): "window_auto_open_threshold", "window_auto_close_threshold", "window_auto_max_duration", + "window_action", "motion_sensor_entity_id", "presence_sensor_entity_id", "power_sensor_entity_id", @@ -203,6 +205,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): "temperature_unit", "is_device_active", "target_temperature_step", + "is_used_by_central_boiler", } ) ) @@ -266,8 +269,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity): self._window_auto_state = False self._window_auto_on = False self._window_auto_algo = None - # PR - Adding Window ByPass self._window_bypass_state = False + self._window_action = None self._current_tz = dt_util.get_time_zone(self._hass.config.time_zone) @@ -283,6 +286,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity): self._is_central_mode = None self._last_central_mode = None + self._is_used_by_central_boiler = False + self.post_init(entry_infos) def clean_central_config_doublon(self, config_entry, central_config) -> dict: @@ -472,7 +477,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): self._saved_preset_mode = PRESET_NONE # Power management - self._device_power = entry_infos.get(CONF_DEVICE_POWER) + self._device_power = entry_infos.get(CONF_DEVICE_POWER) or 0 self._pmax_on = False self._current_power = None self._current_power_max = None @@ -569,6 +574,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity): entry_infos.get(CONF_USE_CENTRAL_MODE) is False ) # Default value (None) is True + self._is_used_by_central_boiler = ( + entry_infos.get(CONF_USED_BY_CENTRAL_BOILER) is True + ) + + self._window_action = ( + entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF + ) + _LOGGER.debug( "%s - Creation of a new VersatileThermostat entity: unique_id=%s", self, @@ -1010,6 +1023,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity): action = HVACAction.HEATING return action + @property + def is_used_by_central_boiler(self) -> HVACAction | None: + """Return true is the VTherm is configured to be used by + central boiler""" + return self._is_used_by_central_boiler + @property def target_temperature(self): """Return the temperature we try to reach.""" @@ -1079,6 +1098,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity): """Get the Window Bypass""" return self._window_bypass_state + @property + def window_action(self) -> bool | None: + """Get the Window Action""" + return self._window_action + @property def security_state(self) -> bool | None: """Get the security_state""" @@ -1142,6 +1166,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity): """Returns the number of underlying entities""" return len(self._underlyings) + @property + def underlying_entities(self) -> int: + """Returns the underlying entities""" + return self._underlyings + @property def is_on(self) -> bool: """True if the VTherm is on (! HVAC_OFF)""" @@ -1468,29 +1497,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity): self._window_state = new_state.state == STATE_ON - # PR - Adding Window ByPass _LOGGER.debug("%s - Window ByPass is : %s", self, self._window_bypass_state) if self._window_bypass_state: _LOGGER.info( "%s - Window ByPass is activated. Ignore window event", self ) else: - if not self._window_state: - _LOGGER.info( - "%s - Window is closed. Restoring hvac_mode '%s' if central_mode is not STOPPED", - self, - self._saved_hvac_mode, - ) - if self.last_central_mode != CENTRAL_MODE_STOPPED: - await self.restore_hvac_mode(True) - elif self._window_state: - _LOGGER.info( - "%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF - ) - if self.last_central_mode in [CENTRAL_MODE_AUTO, None]: - self.save_hvac_mode() + await self.change_window_detection_state(self._window_state) - await self.async_set_hvac_mode(HVACMode.OFF) self.update_custom_attributes() if new_state is None or old_state is None or new_state.state == old_state.state: @@ -1619,6 +1633,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity): for under in self._underlyings: await under.check_initial_state(self._hvac_mode) + # Starts the initial control loop (don't wait for an update of temperature) + await self.async_control_heating(force=True) + @callback async def _async_update_temp(self, state: State): """Update thermostat with latest state from sensor.""" @@ -1644,7 +1661,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): # try to restart if we were in safety mode if self._security_state: - await self.check_security() + await self.check_safety() # check window_auto return await self._async_manage_window_auto() @@ -1671,7 +1688,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): # try to restart if we were in safety mode if self._security_state: - await self.check_security() + await self.check_safety() except ValueError as ex: _LOGGER.error("Unable to update external temperature from sensor: %s", ex) @@ -1828,7 +1845,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity): ) # Set attributes self._window_auto_state = False - await self.restore_hvac_mode(True) + await self.change_window_detection_state(self._window_auto_state) + # await self.restore_hvac_mode(True) if self._window_call_cancel: self._window_call_cancel() @@ -1855,7 +1873,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity): ) if self.window_bypass_state or not self.is_window_auto_enabled: - _LOGGER.info("%s - Window auto event is ignored because bypass is ON or window auto detection is disabled", self) + _LOGGER.info( + "%s - Window auto event is ignored because bypass is ON or window auto detection is disabled", + self, + ) return if ( @@ -1885,8 +1906,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity): ) # Set attributes self._window_auto_state = True - self.save_hvac_mode() - await self.async_set_hvac_mode(HVACMode.OFF) + await self.change_window_detection_state(self._window_auto_state) + # self.save_hvac_mode() + # await self.async_set_hvac_mode(HVACMode.OFF) # Arm the end trigger if self._window_call_cancel: @@ -2106,7 +2128,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): """Get now. The local datetime or the overloaded _set_now date""" return self._now if self._now is not None else datetime.now(self._current_tz) - async def check_security(self) -> bool: + async def check_safety(self) -> bool: """Check if last temperature date is too long""" now = self.now delta_temp = ( @@ -2118,9 +2140,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity): mode_cond = self._hvac_mode != HVACMode.OFF - temp_cond: bool = ( - delta_temp > self._security_delay_min - or delta_ext_temp > self._security_delay_min + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api() + is_outdoor_checked = ( + not api.safety_mode + or api.safety_mode.get("check_outdoor_sensor") is not False + ) + + temp_cond: bool = delta_temp > self._security_delay_min or ( + is_outdoor_checked and delta_ext_temp > self._security_delay_min ) climate_cond: bool = self.is_over_climate and self.hvac_action not in [ HVACAction.COOLING, @@ -2264,6 +2291,63 @@ class BaseThermostat(ClimateEntity, RestoreEntity): should have found the underlying climate to be operational""" return True + async def change_window_detection_state(self, new_state): + """Change the window detection state. + new_state is on if an open window have been detected or off else + """ + if not new_state: + _LOGGER.info( + "%s - Window is closed. Restoring hvac_mode '%s' if central_mode is not STOPPED", + self, + self._saved_hvac_mode, + ) + if self._window_action in [CONF_WINDOW_FROST_TEMP, CONF_WINDOW_ECO_TEMP]: + await self._async_internal_set_temperature(self._saved_target_temp) + # default to TURN_OFF + elif self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]: + if self.last_central_mode != CENTRAL_MODE_STOPPED: + await self.restore_hvac_mode(True) + else: + _LOGGER.error( + "%s - undefined window_action %s. Please open a bug in the github of this project with this log", + self, + self._window_action, + ) + else: + _LOGGER.info( + "%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF + ) + if self.last_central_mode in [CENTRAL_MODE_AUTO, None]: + if self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]: + self.save_hvac_mode() + elif self._window_action in [ + CONF_WINDOW_FROST_TEMP, + CONF_WINDOW_ECO_TEMP, + ]: + self._saved_target_temp = self._target_temp + + if ( + self._window_action == CONF_WINDOW_FAN_ONLY + and HVACMode.FAN_ONLY in self.hvac_modes + ): + await self.async_set_hvac_mode(HVACMode.FAN_ONLY) + elif ( + self._window_action == CONF_WINDOW_FROST_TEMP + and self._presets.get(PRESET_FROST_PROTECTION) is not None + ): + await self._async_internal_set_temperature( + self.find_preset_temp(PRESET_FROST_PROTECTION) + ) + elif ( + self._window_action == CONF_WINDOW_ECO_TEMP + and self._presets.get(PRESET_ECO) is not None + ): + await self._async_internal_set_temperature( + self.find_preset_temp(PRESET_ECO) + ) + else: # default is to turn_off + await self.async_set_hvac_mode(HVACMode.OFF) + async def async_control_heating(self, force=False, _=None): """The main function used to run the calculation at each cycle""" @@ -2291,7 +2375,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity): _LOGGER.debug("%s - End of cycle (overpowering)", self) return True - security: bool = await self.check_security() + security: bool = await self.check_safety() if security and self.is_over_climate: _LOGGER.debug("%s - End of cycle (security and over climate)", self) return True @@ -2357,8 +2441,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity): self.get_preset_away_name(PRESET_COMFORT) ), "power_temp": self._power_temp, - # Already in super class - "target_temp": self.target_temperature, - # Already in super class - "current_temp": self._cur_temp, "target_temperature_step": self.target_temperature_step, "ext_current_temperature": self._cur_ext_temp, "ac_mode": self._ac_mode, @@ -2367,12 +2449,23 @@ class BaseThermostat(ClimateEntity, RestoreEntity): "saved_preset_mode": self._saved_preset_mode, "saved_target_temp": self._saved_target_temp, "saved_hvac_mode": self._saved_hvac_mode, - "window_state": self.window_state, + "motion_sensor_entity_id": self._motion_sensor_entity_id, "motion_state": self._motion_state, + "power_sensor_entity_id": self._power_sensor_entity_id, + "max_power_sensor_entity_id": self._max_power_sensor_entity_id, "overpowering_state": self.overpowering_state, + "presence_sensor_entity_id": self._presence_sensor_entity_id, "presence_state": self._presence_state, + "window_state": self.window_state, "window_auto_state": self.window_auto_state, "window_bypass_state": self._window_bypass_state, + "window_sensor_entity_id": self._window_sensor_entity_id, + "window_delay_sec": self._window_delay_sec, + "window_auto_enabled": self.is_window_auto_enabled, + "window_auto_open_threshold": self._window_auto_open_threshold, + "window_auto_close_threshold": self._window_auto_close_threshold, + "window_auto_max_duration": self._window_auto_max_duration, + "window_action": self.window_action, "security_delay_min": self._security_delay_min, "security_min_on_percent": self._security_min_on_percent, "security_default_on_percent": self._security_default_on_percent, @@ -2391,19 +2484,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity): .astimezone(self._current_tz) .isoformat(), "timezone": str(self._current_tz), - "window_sensor_entity_id": self._window_sensor_entity_id, - "window_delay_sec": self._window_delay_sec, - "window_auto_enabled": self.is_window_auto_enabled, - "window_auto_open_threshold": self._window_auto_open_threshold, - "window_auto_close_threshold": self._window_auto_close_threshold, - "window_auto_max_duration": self._window_auto_max_duration, - "motion_sensor_entity_id": self._motion_sensor_entity_id, - "presence_sensor_entity_id": self._presence_sensor_entity_id, - "power_sensor_entity_id": self._power_sensor_entity_id, - "max_power_sensor_entity_id": self._max_power_sensor_entity_id, "temperature_unit": self.temperature_unit, "is_device_active": self.is_device_active, "ema_temp": self._ema_temp, + "is_used_by_central_boiler": self.is_used_by_central_boiler, } @callback @@ -2526,8 +2610,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity): def send_event(self, event_type: EventType, data: dict): """Send an event""" - _LOGGER.info("%s - Sending event %s with data: %s", self, event_type, data) - data["entity_id"] = self.entity_id - data["name"] = self.name - data["state_attributes"] = self.state_attributes - self._hass.bus.fire(event_type.value, data) + send_vtherm_event(self._hass, event_type=event_type, entity=self, data=data) + # _LOGGER.info("%s - Sending event %s with data: %s", self, event_type, data) + # data["entity_id"] = self.entity_id + # data["name"] = self.name + # data["state_attributes"] = self.state_attributes + # self._hass.bus.fire(event_type.value, data) diff --git a/custom_components/versatile_thermostat/binary_sensor.py b/custom_components/versatile_thermostat/binary_sensor.py index 3e5cc0a..7e30cc9 100644 --- a/custom_components/versatile_thermostat/binary_sensor.py +++ b/custom_components/versatile_thermostat/binary_sensor.py @@ -1,9 +1,20 @@ """ Implements the VersatileThermostat binary sensors component """ +# pylint: disable=unused-argument, line-too-long + import logging -from homeassistant.core import HomeAssistant, callback, Event +from homeassistant.core import ( + HomeAssistant, + callback, + Event, + CoreState, + HomeAssistantError, +) -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START + +from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.components.binary_sensor import ( BinarySensorEntity, @@ -13,8 +24,14 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .commons import VersatileThermostatBaseEntity +from .vtherm_api import VersatileThermostatAPI +from .commons import ( + VersatileThermostatBaseEntity, + check_and_extract_service_configuration, +) from .const import ( + DOMAIN, + DEVICE_MANUFACTURER, CONF_NAME, CONF_USE_POWER_FEATURE, CONF_USE_PRESENCE_FEATURE, @@ -22,6 +39,11 @@ from .const import ( CONF_USE_WINDOW_FEATURE, CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_CENTRAL_CONFIG, + CONF_CENTRAL_BOILER_ACTIVATION_SRV, + CONF_CENTRAL_BOILER_DEACTIVATION_SRV, + overrides, + EventType, + send_vtherm_event, ) _LOGGER = logging.getLogger(__name__) @@ -42,20 +64,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 +293,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 +330,161 @@ 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) + self._entities = [] + self._hass = hass + self._service_activate = check_and_extract_service_configuration( + entry_infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV) + ) + self._service_deactivate = check_and_extract_service_configuration( + entry_infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV) + ) + + @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" + + @overrides + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass) + api.register_central_boiler(self) + + @callback + async def _async_startup_internal(*_): + _LOGGER.debug("%s - Calling async_startup_internal", self) + await self.listen_nb_active_vtherm_entity() + + if self.hass.state == CoreState.running: + await _async_startup_internal() + else: + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, _async_startup_internal + ) + + async def listen_nb_active_vtherm_entity(self): + """Initialize the listening of state change of VTherms""" + + # Listen to all VTherm state change + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass) + + if ( + api.nb_active_device_for_boiler_entity + and api.nb_active_device_for_boiler_threshold_entity + ): + listener_cancel = async_track_state_change_event( + self._hass, + [ + api.nb_active_device_for_boiler_entity.entity_id, + api.nb_active_device_for_boiler_threshold_entity.entity_id, + ], + self.calculate_central_boiler_state, + ) + _LOGGER.debug( + "%s - entity to get the nb of active VTherm is %s", + self, + api.nb_active_device_for_boiler_entity.entity_id, + ) + self.async_on_remove(listener_cancel) + else: + _LOGGER.debug("%s - no VTherm could controls the central boiler", self) + + await self.calculate_central_boiler_state(None) + + async def calculate_central_boiler_state(self, _): + """Calculate the central boiler state depending on all VTherm that + controls this central boiler""" + + _LOGGER.debug("%s - calculating the new central boiler state", self) + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass) + if ( + api.nb_active_device_for_boiler is None + or api.nb_active_device_for_boiler_threshold is None + ): + _LOGGER.warning( + "%s - the entities to calculate the boiler state are not initialized. Boiler state cannot be calculated", + self, + ) + return False + + active = ( + api.nb_active_device_for_boiler >= api.nb_active_device_for_boiler_threshold + ) + + if self._attr_is_on != active: + try: + if active: + await self.call_service(self._service_activate) + _LOGGER.info("%s - central boiler have been turned on", self) + else: + await self.call_service(self._service_deactivate) + _LOGGER.info("%s - central boiler have been turned off", self) + self._attr_is_on = active + send_vtherm_event( + hass=self._hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=self, + data={"central_boiler": active}, + ) + self.async_write_ha_state() + except HomeAssistantError as err: + _LOGGER.error( + "%s - Impossible to activate/deactivat boiler due to error %s." + "Central boiler will not being controled by VTherm." + "Please check your service configuration. Cf. README.", + self, + err, + ) + + async def call_service(self, service_config: dict): + """Make a call to a service if correctly configured""" + if not service_config: + return + + await self._hass.services.async_call( + service_config["service_domain"], + service_config["service_name"], + service_data=service_config["data"], + target={ + "entity_id": service_config["entity_id"], + }, + ) + + def __str__(self): + return f"VersatileThermostat-{self.name}" diff --git a/custom_components/versatile_thermostat/commons.py b/custom_components/versatile_thermostat/commons.py index 4c99eff..13b1477 100644 --- a/custom_components/versatile_thermostat/commons.py +++ b/custom_components/versatile_thermostat/commons.py @@ -1,4 +1,6 @@ """ Some usefull commons class """ +# pylint: disable=line-too-long + import logging from datetime import timedelta, datetime from homeassistant.core import HomeAssistant, callback, Event @@ -10,41 +12,148 @@ from homeassistant.helpers.event import async_track_state_change_event, async_ca from homeassistant.util import dt as dt_util from .base_thermostat import BaseThermostat -from .const import DOMAIN, DEVICE_MANUFACTURER +from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError _LOGGER = logging.getLogger(__name__) + def get_tz(hass: HomeAssistant): """Get the current timezone""" return dt_util.get_time_zone(hass.config.time_zone) + class NowClass: - """ For testing purpose only""" + """For testing purpose only""" @staticmethod def get_now(hass: HomeAssistant) -> datetime: - """ A test function to get the now. - For testing purpose this method can be overriden to get a specific - timestamp. + """A test function to get the now. + For testing purpose this method can be overriden to get a specific + timestamp. """ - return datetime.now( get_tz(hass)) + return datetime.now(get_tz(hass)) -def round_to_nearest(n:float, x: float)->float: - """ Round a number to the nearest x (which should be decimal but not null) - Example: - nombre1 = 3.2 - nombre2 = 4.7 - x = 0.3 - nombre_arrondi1 = round_to_nearest(nombre1, x) - nombre_arrondi2 = round_to_nearest(nombre2, x) +def round_to_nearest(n: float, x: float) -> float: + """Round a number to the nearest x (which should be decimal but not null) + Example: + nombre1 = 3.2 + nombre2 = 4.7 + x = 0.3 - print(nombre_arrondi1) # Output: 3.3 - print(nombre_arrondi2) # Output: 4.6 + nombre_arrondi1 = round_to_nearest(nombre1, x) + nombre_arrondi2 = round_to_nearest(nombre2, x) + + print(nombre_arrondi1) # Output: 3.3 + print(nombre_arrondi2) # Output: 4.6 """ assert x > 0 - return round(n * (1/x)) / (1/x) + return round(n * (1 / x)) / (1 / x) + + +def check_and_extract_service_configuration(service_config) -> dict: + """Raise a ServiceConfigurationError. In return you have a dict formatted like follows. + Example if you call with 'climate.central_boiler/climate.set_temperature/temperature:10': + { + "service_domain": "climate", + "service_name": "set_temperature", + "entity_id": "climate.central_boiler", + "entity_domain": "climate", + "entity_name": "central_boiler", + "data": { + "temperature": "10" + }, + "attribute_name": "temperature", + "attribute_value: "10" + } + + For this example 'switch.central_boiler/switch.turn_off' you will have this: + { + "service_domain": "switch", + "service_name": "turn_off", + "entity_id": "switch.central_boiler", + "entity_domain": "switch", + "entity_name": "central_boiler", + "data": { }, + } + + All values are striped (white space are removed) and are string + """ + + ret = {} + + if service_config is None: + return ret + + parties = service_config.split("/") + if len(parties) < 2: + raise ServiceConfigurationError( + f"Incorrect service configuration. Service {service_config} should be formatted with: 'entity_name/service_name[/data]'. See README for more information." + ) + entity_id = parties[0] + service_name = parties[1] + + service_infos = service_name.split(".") + if len(service_infos) != 2: + raise ServiceConfigurationError( + f"Incorrect service configuration. The service {service_config} should be formatted like: 'domain.service_name' (ex: 'switch.turn_on'). See README for more information." + ) + + ret.update( + { + "service_domain": service_infos[0].strip(), + "service_name": service_infos[1].strip(), + } + ) + + entity_infos = entity_id.split(".") + if len(entity_infos) != 2: + raise ServiceConfigurationError( + f"Incorrect service configuration. The entity_id {entity_id} should be formatted like: 'domain.entity_name' (ex: 'switch.central_boiler_switch'). See README for more information." + ) + + ret.update( + { + "entity_domain": entity_infos[0].strip(), + "entity_name": entity_infos[1].strip(), + "entity_id": entity_id.strip(), + } + ) + + if len(parties) == 3: + data = parties[2] + if len(data) > 0: + data_infos = None + data_infos = data.split(":") + if ( + len(data_infos) != 2 + or len(data_infos[0]) <= 0 + or len(data_infos[1]) <= 0 + ): + raise ServiceConfigurationError( + f"Incorrect service configuration. The data {data} should be formatted like: 'attribute:value' (ex: 'value:25'). See README for more information." + ) + + ret.update( + { + "attribute_name": data_infos[0].strip(), + "attribute_value": data_infos[1].strip(), + "data": {data_infos[0].strip(): data_infos[1].strip()}, + } + ) + else: + raise ServiceConfigurationError( + f"Incorrect service configuration. The data {data} should be formatted like: 'attribute:value' (ex: 'value:25'). See README for more information." + ) + else: + ret.update({"data": {}}) + + _LOGGER.debug( + "check_and_extract_service_configuration(%s) gives '%s'", service_config, ret + ) + return ret + class VersatileThermostatBaseEntity(Entity): """A base class for all entities""" @@ -130,7 +239,9 @@ class VersatileThermostatBaseEntity(Entity): await try_find_climate(None) @callback - async def async_my_climate_changed(self, event: Event): # pylint: disable=unused-argument + async def async_my_climate_changed( + self, event: Event + ): # pylint: disable=unused-argument """Called when my climate have change This method aims to be overriden to take the status change """ diff --git a/custom_components/versatile_thermostat/config_flow.py b/custom_components/versatile_thermostat/config_flow.py index 02c7be9..2da46f6 100644 --- a/custom_components/versatile_thermostat/config_flow.py +++ b/custom_components/versatile_thermostat/config_flow.py @@ -23,6 +23,7 @@ from homeassistant.data_entry_flow import FlowHandler, FlowResult from .const import * # pylint: disable=wildcard-import, unused-wildcard-import from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import from .vtherm_api import VersatileThermostatAPI +from .commons import check_and_extract_service_configuration COMES_FROM = "comes_from" @@ -191,6 +192,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): ) raise NoCentralConfig(conf) + # Check the service for central boiler format + if self._infos.get(CONF_ADD_CENTRAL_BOILER_CONTROL): + for conf in [ + CONF_CENTRAL_BOILER_ACTIVATION_SRV, + CONF_CENTRAL_BOILER_DEACTIVATION_SRV, + ]: + try: + check_and_extract_service_configuration(data.get(conf)) + except ServiceConfigurationError as err: + raise ServiceConfigurationError(conf) from err + def merge_user_input(self, data_schema: vol.Schema, user_input: dict): """For each schema entry not in user_input, set or remove values in infos""" self._infos.update(user_input) @@ -225,6 +237,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): errors[str(err)] = "window_open_detection_method" except NoCentralConfig as err: errors[str(err)] = "no_central_config" + except ServiceConfigurationError as err: + errors[str(err)] = "service_configuration_format" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -263,7 +277,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 @@ -278,7 +295,10 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): """Handle the specific main flow steps""" _LOGGER.debug("Into ConfigFlow.async_step_spec_main user_input=%s", user_input) - schema = STEP_CENTRAL_MAIN_DATA_SCHEMA + if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG: + schema = STEP_CENTRAL_MAIN_DATA_SCHEMA + else: + schema = STEP_CENTRAL_SPEC_MAIN_DATA_SCHEMA next_step = self.async_step_type self._infos[COMES_FROM] = "async_step_spec_main" @@ -286,6 +306,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..fc9562f 100644 --- a/custom_components/versatile_thermostat/config_schema.py +++ b/custom_components/versatile_thermostat/config_schema.py @@ -28,7 +28,9 @@ STEP_USER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH ): selector.SelectSelector( selector.SelectSelectorConfig( - options=CONF_THERMOSTAT_TYPES, translation_key="thermostat_type" + options=CONF_THERMOSTAT_TYPES, + translation_key="thermostat_type", + mode="list", ) ) } @@ -43,11 +45,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 +61,24 @@ 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_SPEC_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name + { + vol.Required(CONF_EXTERNAL_TEMP_SENSOR): selector.EntitySelector( + selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]), + ), + vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float), + vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float), + } +) + +STEP_CENTRAL_BOILER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_CENTRAL_BOILER_ACTIVATION_SRV, default=""): str, + vol.Optional(CONF_CENTRAL_BOILER_DEACTIVATION_SRV, default=""): str, } ) @@ -106,6 +127,7 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name selector.SelectSelectorConfig( options=CONF_AUTO_REGULATION_MODES, translation_key="auto_regulation_mode", + mode="dropdown", ) ), vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(float), @@ -116,6 +138,7 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name selector.SelectSelectorConfig( options=CONF_AUTO_FAN_MODES, translation_key="auto_fan_mode", + mode="dropdown", ) ), } @@ -193,12 +216,30 @@ STEP_CENTRAL_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name vol.Optional(CONF_WINDOW_AUTO_OPEN_THRESHOLD, default=3): vol.Coerce(float), vol.Optional(CONF_WINDOW_AUTO_CLOSE_THRESHOLD, default=0): vol.Coerce(float), vol.Optional(CONF_WINDOW_AUTO_MAX_DURATION, default=30): cv.positive_int, + vol.Optional( + CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=CONF_WINDOW_ACTIONS, + translation_key="window_action", + mode="dropdown", + ) + ), } ) STEP_CENTRAL_WINDOW_WO_AUTO_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name { vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int, + vol.Optional( + CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=CONF_WINDOW_ACTIONS, + translation_key="window_action", + mode="dropdown", + ) + ), } ) @@ -217,11 +258,19 @@ STEP_CENTRAL_MOTION_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name { vol.Optional(CONF_MOTION_DELAY, default=30): cv.positive_int, vol.Optional(CONF_MOTION_OFF_DELAY, default=300): cv.positive_int, - vol.Optional(CONF_MOTION_PRESET, default="comfort"): vol.In( - CONF_PRESETS_SELECTIONABLE + vol.Optional(CONF_MOTION_PRESET, default="comfort"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=CONF_PRESETS_SELECTIONABLE, + translation_key="presets", + mode="dropdown", + ) ), - vol.Optional(CONF_NO_MOTION_PRESET, default="eco"): vol.In( - CONF_PRESETS_SELECTIONABLE + vol.Optional(CONF_NO_MOTION_PRESET, default="eco"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=CONF_PRESETS_SELECTIONABLE, + translation_key="presets", + mode="dropdown", + ) ), } ) diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index 794d70d..b4dd513 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -1,6 +1,8 @@ # pylint: disable=line-too-long """Constants for the Versatile Thermostat integration.""" +import logging + from enum import Enum from homeassistant.const import CONF_NAME, Platform @@ -18,6 +20,8 @@ from .prop_algorithm import ( PROPORTIONAL_FUNCTION_TPI, ) +_LOGGER = logging.getLogger(__name__) + PRESET_AC_SUFFIX = "_ac" PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX PRESET_COMFORT_AC = PRESET_COMFORT + PRESET_AC_SUFFIX @@ -36,10 +40,11 @@ HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY] DOMAIN = "versatile_thermostat" PLATFORMS: list[Platform] = [ - Platform.CLIMATE, - Platform.BINARY_SENSOR, - Platform.SENSOR, + Platform.NUMBER, Platform.SELECT, + Platform.CLIMATE, + Platform.SENSOR, + Platform.BINARY_SENSOR, ] CONF_HEATER = "heater_entity_id" @@ -101,7 +106,6 @@ CONF_AUTO_REGULATION_EXPERT = "auto_regulation_expert" CONF_AUTO_REGULATION_DTEMP = "auto_regulation_dtemp" CONF_AUTO_REGULATION_PERIOD_MIN = "auto_regulation_periode_min" CONF_INVERSE_SWITCH = "inverse_switch_command" -CONF_SHORT_EMA_PARAMS = "short_ema_params" CONF_AUTO_FAN_MODE = "auto_fan_mode" CONF_AUTO_FAN_NONE = "auto_fan_none" CONF_AUTO_FAN_LOW = "auto_fan_low" @@ -109,6 +113,10 @@ CONF_AUTO_FAN_MEDIUM = "auto_fan_medium" CONF_AUTO_FAN_HIGH = "auto_fan_high" CONF_AUTO_FAN_TURBO = "auto_fan_turbo" +# Global params into configuration.yaml +CONF_SHORT_EMA_PARAMS = "short_ema_params" +CONF_SAFETY_MODE = "safety_mode" + CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config" CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config" CONF_USE_WINDOW_CENTRAL_CONFIG = "use_window_central_config" @@ -120,6 +128,13 @@ 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" +CONF_WINDOW_ACTION = "window_action" + DEFAULT_SHORT_EMA_PARAMS = { "max_alpha": 0.5, # In sec @@ -250,6 +265,11 @@ 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_WINDOW_ACTION, ] + CONF_PRESETS_VALUES + CONF_PRESETS_AWAY_VALUES @@ -285,6 +305,18 @@ CONF_AUTO_FAN_MODES = [ CONF_AUTO_FAN_TURBO, ] +CONF_WINDOW_TURN_OFF = "window_turn_off" +CONF_WINDOW_FAN_ONLY = "window_fan_only" +CONF_WINDOW_FROST_TEMP = "window_frost_temp" +CONF_WINDOW_ECO_TEMP = "window_eco_temp" + +CONF_WINDOW_ACTIONS = [ + CONF_WINDOW_TURN_OFF, + CONF_WINDOW_FAN_ONLY, + CONF_WINDOW_FROST_TEMP, + CONF_WINDOW_ECO_TEMP, +] + SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE SERVICE_SET_PRESENCE = "set_presence" @@ -393,10 +425,20 @@ class EventType(Enum): POWER_EVENT: str = "versatile_thermostat_power_event" TEMPERATURE_EVENT: str = "versatile_thermostat_temperature_event" HVAC_MODE_EVENT: str = "versatile_thermostat_hvac_mode_event" + CENTRAL_BOILER_EVENT: str = "versatile_thermostat_central_boiler_event" PRESET_EVENT: str = "versatile_thermostat_preset_event" WINDOW_AUTO_EVENT: str = "versatile_thermostat_window_auto_event" +def send_vtherm_event(hass, event_type: EventType, entity, data: dict): + """Send an event""" + _LOGGER.info("%s - Sending event %s with data: %s", entity, event_type, data) + data["entity_id"] = entity.entity_id + data["name"] = entity.name + data["state_attributes"] = entity.state_attributes + hass.bus.fire(event_type.value, data) + + class UnknownEntity(HomeAssistantError): """Error to indicate there is an unknown entity_id given.""" @@ -409,6 +451,10 @@ class NoCentralConfig(HomeAssistantError): """Error to indicate that we try to use a central configuration but no VTherm of type CENTRAL CONFIGURATION has been found""" +class ServiceConfigurationError(HomeAssistantError): + """Error in the service configuration to control the central boiler""" + + class overrides: # pylint: disable=invalid-name """An annotation to inform overrides""" diff --git a/custom_components/versatile_thermostat/manifest.json b/custom_components/versatile_thermostat/manifest.json index 1dd8522..037bf65 100644 --- a/custom_components/versatile_thermostat/manifest.json +++ b/custom_components/versatile_thermostat/manifest.json @@ -14,6 +14,6 @@ "quality_scale": "silver", "requirements": [], "ssdp": [], - "version": "5.2.2", + "version": "5.3.0", "zeroconf": [] -} +} \ No newline at end of file diff --git a/custom_components/versatile_thermostat/number.py b/custom_components/versatile_thermostat/number.py new file mode 100644 index 0000000..416f955 --- /dev/null +++ b/custom_components/versatile_thermostat/number.py @@ -0,0 +1,117 @@ +# pylint: disable=unused-argument + +""" Implements the VersatileThermostat select component """ +import logging + +# from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.core import HomeAssistant, CoreState # , callback + +from homeassistant.components.number import NumberEntity, NumberMode +from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.entity_platform import AddEntitiesCallback + + +from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI +from .const import ( + DOMAIN, + DEVICE_MANUFACTURER, + CONF_NAME, + CONF_THERMOSTAT_TYPE, + CONF_THERMOSTAT_CENTRAL_CONFIG, + CONF_ADD_CENTRAL_BOILER_CONTROL, + overrides, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the VersatileThermostat selects with config flow.""" + _LOGGER.debug( + "Calling async_setup_entry entry=%s, data=%s", entry.entry_id, entry.data + ) + + unique_id = entry.entry_id + name = entry.data.get(CONF_NAME) + vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) + is_central_boiler = entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL) + + if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG or not is_central_boiler: + return + + entities = [ + ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data), + ] + + async_add_entities(entities, True) + + +class ActivateBoilerThresholdNumber(NumberEntity, RestoreEntity): + """Representation of the threshold of the number of VTherm + which should be active to activate the boiler""" + + def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: + """Initialize the energy sensor""" + self._hass = hass + self._config_id = unique_id + self._device_name = entry_infos.get(CONF_NAME) + self._attr_name = "Boiler Activation threshold" + self._attr_unique_id = "boiler_activation_threshold" + self._attr_value = self._attr_native_value = 1 # default value + self._attr_native_min_value = 1 + self._attr_native_max_value = 9 + self._attr_step = 1 # default value + self._attr_mode = NumberMode.AUTO + + @property + def icon(self) -> str | None: + if isinstance(self._attr_native_value, int): + val = int(self._attr_native_value) + return f"mdi:numeric-{val}-box-outline" + else: + return "mdi:numeric-0-box-outline" + + @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, + ) + + @overrides + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass) + api.register_central_boiler_activation_number_threshold(self) + + old_state: CoreState = await self.async_get_last_state() + _LOGGER.debug( + "%s - Calling async_added_to_hass old_state is %s", self, old_state + ) + if old_state is not None: + self._attr_value = self._attr_native_value = int(float(old_state.state)) + + @overrides + def set_native_value(self, value: float) -> None: + """Change the value""" + int_value = int(value) + old_value = int(self._attr_native_value) + + if int_value == old_value: + return + + self._attr_value = self._attr_native_value = int_value + + def __str__(self): + return f"VersatileThermostat-{self.name}" diff --git a/custom_components/versatile_thermostat/select.py b/custom_components/versatile_thermostat/select.py index 8c77ec6..0ed16bd 100644 --- a/custom_components/versatile_thermostat/select.py +++ b/custom_components/versatile_thermostat/select.py @@ -55,7 +55,7 @@ async def async_setup_entry( class CentralModeSelect(SelectEntity, RestoreEntity): - """Representation of a Energy sensor which exposes the energy""" + """Representation of the central mode choice""" def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: """Initialize the energy sensor""" diff --git a/custom_components/versatile_thermostat/sensor.py b/custom_components/versatile_thermostat/sensor.py index 4c8bf05..66aae47 100644 --- a/custom_components/versatile_thermostat/sensor.py +++ b/custom_components/versatile_thermostat/sensor.py @@ -3,9 +3,15 @@ import logging import math -from homeassistant.core import HomeAssistant, callback, Event +from homeassistant.core import HomeAssistant, callback, Event, CoreState -from homeassistant.const import UnitOfTime, UnitOfPower, UnitOfEnergy, PERCENTAGE +from homeassistant.const import ( + UnitOfTime, + UnitOfPower, + UnitOfEnergy, + PERCENTAGE, + EVENT_HOMEASSISTANT_START, +) from homeassistant.components.sensor import ( SensorEntity, @@ -16,9 +22,24 @@ from homeassistant.components.sensor import ( from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.components.climate import ( + ClimateEntity, + DOMAIN as CLIMATE_DOMAIN, + HVACAction, + HVACMode, +) + + +from .base_thermostat import BaseThermostat +from .vtherm_api import VersatileThermostatAPI from .commons import VersatileThermostatBaseEntity from .const import ( + DOMAIN, + DEVICE_MANUFACTURER, CONF_NAME, CONF_DEVICE_POWER, CONF_PROP_FUNCTION, @@ -28,6 +49,8 @@ from .const import ( CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_CENTRAL_CONFIG, + CONF_ADD_CENTRAL_BOILER_CONTROL, + overrides, ) THRESHOLD_WATT_KILO = 100 @@ -49,35 +72,43 @@ async def async_setup_entry( name = entry.data.get(CONF_NAME) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) + entities = None + if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG: - return + if entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL): + entities = [ + NbActiveDeviceForBoilerSensor(hass, unique_id, name, entry.data) + ] + async_add_entities(entities, True) + else: + entities = [ + LastTemperatureSensor(hass, unique_id, name, entry.data), + LastExtTemperatureSensor(hass, unique_id, name, entry.data), + TemperatureSlopeSensor(hass, unique_id, name, entry.data), + EMATemperatureSensor(hass, unique_id, name, entry.data), + ] + if entry.data.get(CONF_DEVICE_POWER): + entities.append(EnergySensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_THERMOSTAT_TYPE) in [ + CONF_THERMOSTAT_SWITCH, + CONF_THERMOSTAT_VALVE, + ]: + entities.append(MeanPowerSensor(hass, unique_id, name, entry.data)) - entities = [ - LastTemperatureSensor(hass, unique_id, name, entry.data), - LastExtTemperatureSensor(hass, unique_id, name, entry.data), - TemperatureSlopeSensor(hass, unique_id, name, entry.data), - EMATemperatureSensor(hass, unique_id, name, entry.data), - ] - if entry.data.get(CONF_DEVICE_POWER): - entities.append(EnergySensor(hass, unique_id, name, entry.data)) - if entry.data.get(CONF_THERMOSTAT_TYPE) in [ - CONF_THERMOSTAT_SWITCH, - CONF_THERMOSTAT_VALVE, - ]: - entities.append(MeanPowerSensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI: + entities.append(OnPercentSensor(hass, unique_id, name, entry.data)) + entities.append(OnTimeSensor(hass, unique_id, name, entry.data)) + entities.append(OffTimeSensor(hass, unique_id, name, entry.data)) - if entry.data.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI: - entities.append(OnPercentSensor(hass, unique_id, name, entry.data)) - entities.append(OnTimeSensor(hass, unique_id, name, entry.data)) - entities.append(OffTimeSensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE: + entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data)) - if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE: - entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data)) + if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE: + entities.append( + RegulatedTemperatureSensor(hass, unique_id, name, entry.data) + ) - if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE: - entities.append(RegulatedTemperatureSensor(hass, unique_id, name, entry.data)) - - async_add_entities(entities, True) + async_add_entities(entities, True) class EnergySensor(VersatileThermostatBaseEntity, SensorEntity): @@ -597,3 +628,116 @@ class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity): def suggested_display_precision(self) -> int | None: """Return the suggested number of decimal digits for display.""" return 2 + + +class NbActiveDeviceForBoilerSensor(SensorEntity): + """Representation of the threshold of the number of VTherm + which should be active to activate the boiler""" + + def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: + """Initialize the energy sensor""" + self._hass = hass + self._config_id = unique_id + self._device_name = entry_infos.get(CONF_NAME) + self._attr_name = "Nb device active for boiler" + self._attr_unique_id = "nb_device_active_boiler" + self._attr_value = self._attr_native_value = None # default value + self._entities = [] + + @property + def icon(self) -> str | None: + return "mdi:heat-wave" + + @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 state_class(self) -> SensorStateClass | None: + return SensorStateClass.MEASUREMENT + + @property + def suggested_display_precision(self) -> int | None: + """Return the suggested number of decimal digits for display.""" + return 0 + + @overrides + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + + api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass) + api.register_nb_device_active_boiler(self) + + @callback + async def _async_startup_internal(*_): + _LOGGER.debug("%s - Calling async_startup_internal", self) + await self.listen_vtherms_entities() + + if self.hass.state == CoreState.running: + await _async_startup_internal() + else: + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, _async_startup_internal + ) + + async def listen_vtherms_entities(self): + """Initialize the listening of state change of VTherms""" + + # Listen to all VTherm state change + self._entities = [] + underlying_entities_id = [] + + component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN] + for entity in component.entities: + if isinstance(entity, BaseThermostat) and entity.is_used_by_central_boiler: + self._entities.append(entity) + for under in entity.underlying_entities: + underlying_entities_id.append(under.entity_id) + if len(underlying_entities_id) > 0: + # Arme l'écoute de la première entité + listener_cancel = async_track_state_change_event( + self._hass, + underlying_entities_id, + self.calculate_nb_active_devices, + ) + _LOGGER.info( + "%s - the underlyings that could controls the central boiler are %s", + self, + underlying_entities_id, + ) + self.async_on_remove(listener_cancel) + else: + _LOGGER.debug("%s - no VTherm could controls the central boiler", self) + + await self.calculate_nb_active_devices(None) + + async def calculate_nb_active_devices(self, _): + """Calculate the number of active VTherm that have an + influence on central boiler""" + + _LOGGER.debug("%s - calculating the number of active VTherm", self) + nb_active = 0 + for entity in self._entities: + _LOGGER.debug( + "Examining the hvac_action of %s", + entity.name, + ) + if ( + entity.hvac_mode == HVACMode.HEAT + and entity.hvac_action == HVACAction.HEATING + ): + for under in entity.underlying_entities: + nb_active += 1 if under.is_device_active else 0 + + self._attr_native_value = nb_active + self.async_write_ha_state() + + def __str__(self): + return f"VersatileThermostat-{self.name}" diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index 9bd25ee..5cae3ba 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" } }, @@ -130,7 +130,8 @@ "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", - "use_window_central_config": "Use central window configuration" + "use_window_central_config": "Use central window configuration", + "window_action": "Action" }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", @@ -138,7 +139,8 @@ "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", - "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm" + "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", + "window_action": "Action to do if window is deteted as open" } }, "motion": { @@ -256,16 +258,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" } }, @@ -362,7 +364,8 @@ "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", - "use_window_central_config": "Use central window configuration" + "use_window_central_config": "Use central window configuration", + "window_action": "Action" }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", @@ -370,7 +373,8 @@ "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", - "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm" + "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", + "window_action": "Action to do if window is deteted as open" } }, "motion": { @@ -458,7 +462,8 @@ "unknown": "Unexpected error", "unknown_entity": "Unknown entity id", "window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both", - "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it." + "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.", + "service_configuration_format": "The format of the service configuration is wrong" }, "abort": { "already_configured": "Device is already configured" @@ -491,6 +496,22 @@ "auto_fan_high": "High", "auto_fan_turbo": "Turbo" } + }, + "window_action": { + "options": { + "window_turn_off": "Turn off", + "window_fan_only": "Fan only", + "window_frost_temp": "Frost protect", + "window_eco_temp": "Eco" + } + }, + "presets": { + "options": { + "frost": "Frost protect", + "eco": "Eco", + "comfort": "Comfort", + "boost": "Boost" + } } }, "entity": { diff --git a/custom_components/versatile_thermostat/thermostat_climate.py b/custom_components/versatile_thermostat/thermostat_climate.py index 74dad0e..0a4d641 100644 --- a/custom_components/versatile_thermostat/thermostat_climate.py +++ b/custom_components/versatile_thermostat/thermostat_climate.py @@ -526,7 +526,11 @@ class ThermostatOverClimate(BaseThermostat): return added_energy = 0 - if self.is_over_climate and self._underlying_climate_delta_t is not None: + if ( + self.is_over_climate + and self._underlying_climate_delta_t is not None + and self._device_power + ): added_energy = self._device_power * self._underlying_climate_delta_t self._total_energy += added_energy diff --git a/custom_components/versatile_thermostat/thermostat_switch.py b/custom_components/versatile_thermostat/thermostat_switch.py index ff50a41..761c244 100644 --- a/custom_components/versatile_thermostat/thermostat_switch.py +++ b/custom_components/versatile_thermostat/thermostat_switch.py @@ -208,5 +208,6 @@ class ThermostatOverSwitch(BaseThermostat): return if old_state is None: self.hass.create_task(self._check_initial_state()) + self.async_write_ha_state() self.update_custom_attributes() diff --git a/custom_components/versatile_thermostat/thermostat_valve.py b/custom_components/versatile_thermostat/thermostat_valve.py index f246a55..bd98081 100644 --- a/custom_components/versatile_thermostat/thermostat_valve.py +++ b/custom_components/versatile_thermostat/thermostat_valve.py @@ -13,7 +13,13 @@ from homeassistant.components.climate import HVACMode from .base_thermostat import BaseThermostat from .prop_algorithm import PropAlgorithm -from .const import CONF_VALVE, CONF_VALVE_2, CONF_VALVE_3, CONF_VALVE_4, overrides +from .const import ( + CONF_VALVE, + CONF_VALVE_2, + CONF_VALVE_3, + CONF_VALVE_4, + overrides, +) from .underlyings import UnderlyingValve diff --git a/custom_components/versatile_thermostat/translations/en.json b/custom_components/versatile_thermostat/translations/en.json index 9bd25ee..5cae3ba 100644 --- a/custom_components/versatile_thermostat/translations/en.json +++ b/custom_components/versatile_thermostat/translations/en.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" } }, @@ -130,7 +130,8 @@ "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", - "use_window_central_config": "Use central window configuration" + "use_window_central_config": "Use central window configuration", + "window_action": "Action" }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", @@ -138,7 +139,8 @@ "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", - "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm" + "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", + "window_action": "Action to do if window is deteted as open" } }, "motion": { @@ -256,16 +258,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" } }, @@ -362,7 +364,8 @@ "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", - "use_window_central_config": "Use central window configuration" + "use_window_central_config": "Use central window configuration", + "window_action": "Action" }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", @@ -370,7 +373,8 @@ "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", - "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm" + "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", + "window_action": "Action to do if window is deteted as open" } }, "motion": { @@ -458,7 +462,8 @@ "unknown": "Unexpected error", "unknown_entity": "Unknown entity id", "window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both", - "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it." + "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.", + "service_configuration_format": "The format of the service configuration is wrong" }, "abort": { "already_configured": "Device is already configured" @@ -491,6 +496,22 @@ "auto_fan_high": "High", "auto_fan_turbo": "Turbo" } + }, + "window_action": { + "options": { + "window_turn_off": "Turn off", + "window_fan_only": "Fan only", + "window_frost_temp": "Frost protect", + "window_eco_temp": "Eco" + } + }, + "presets": { + "options": { + "frost": "Frost protect", + "eco": "Eco", + "comfort": "Comfort", + "boost": "Boost" + } } }, "entity": { diff --git a/custom_components/versatile_thermostat/translations/fr.json b/custom_components/versatile_thermostat/translations/fr.json index 3e12736..eb6d894 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": { @@ -130,7 +130,8 @@ "window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)", "window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)", "window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)", - "use_window_central_config": "Utiliser la configuration centrale des ouvertures" + "use_window_central_config": "Utiliser la configuration centrale des ouvertures", + "window_action": "Action" }, "data_description": { "window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur et pour utiliser la détection automatique", @@ -138,7 +139,8 @@ "window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique", - "use_window_central_config": "Cochez pour utiliser la configuration centrale des ouvertures. Décochez et saisissez les attributs pour utiliser une configuration spécifique des ouvertures" + "use_window_central_config": "Cochez pour utiliser la configuration centrale des ouvertures. Décochez et saisissez les attributs pour utiliser une configuration spécifique des ouvertures", + "window_action": "Action a effectuer si la fenêtre est détectée comme ouverte" } }, "motion": { @@ -220,6 +222,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 +270,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": { @@ -356,7 +370,8 @@ "window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)", "window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)", "window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)", - "use_window_central_config": "Utiliser la configuration centrale des ouvertures" + "use_window_central_config": "Utiliser la configuration centrale des ouvertures", + "window_action": "Action" }, "data_description": { "window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur et pour utiliser la détection automatique", @@ -364,7 +379,8 @@ "window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique", - "use_window_central_config": "Cochez pour utiliser la configuration centrale des ouvertures. Décochez et saisissez les attributs pour utiliser une configuration spécifique des ouvertures" + "use_window_central_config": "Cochez pour utiliser la configuration centrale des ouvertures. Décochez et saisissez les attributs pour utiliser une configuration spécifique des ouvertures", + "window_action": "Action a effectuer si la fenêtre est détectée comme ouverte" } }, "motion": { @@ -446,13 +462,26 @@ "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": { "unknown": "Erreur inattendue", "unknown_entity": "entity id inconnu", "window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.", - "no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser." + "no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.", + "service_configuration_format": "Mauvais format de la configuration du service" }, "abort": { "already_configured": "Le device est déjà configuré" @@ -485,6 +514,22 @@ "auto_fan_high": "Forte", "auto_fan_turbo": "Turbo" } + }, + "window_action": { + "options": { + "window_turn_off": "Eteindre", + "window_fan_only": "Ventilateur seul", + "window_frost_temp": "Hors gel", + "window_eco_temp": "Eco" + } + }, + "presets": { + "options": { + "frost": "Hors-gel", + "eco": "Eco", + "comfort": "Confort", + "boost": "Renforcé (boost)" + } } }, "entity": { diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index 5c10af9..befc515 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -133,14 +133,14 @@ class UnderlyingEntity: async def check_initial_state(self, hvac_mode: HVACMode): """Prevent the underlying to be on but thermostat is off""" if hvac_mode == HVACMode.OFF and self.is_device_active: - _LOGGER.warning( + _LOGGER.info( "%s - The hvac mode is OFF, but the underlying device is ON. Turning off device %s", self, self._entity_id, ) await self.set_hvac_mode(hvac_mode) elif hvac_mode != HVACMode.OFF and not self.is_device_active: - _LOGGER.warning( + _LOGGER.info( "%s - The hvac mode is %s, but the underlying device is not ON. Turning on device %s if needed", self, hvac_mode, @@ -356,7 +356,7 @@ class UnderlyingSwitch(UnderlyingEntity): _LOGGER.debug("%s - End of cycle (3)", self) return # safety mode could have change the on_time percent - await self._thermostat.check_security() + await self._thermostat.check_safety() time = self._on_time_sec action_label = "start" @@ -758,19 +758,25 @@ class UnderlyingValve(UnderlyingEntity): async def turn_off(self): """Turn heater toggleable device off.""" _LOGGER.debug("%s - Stopping underlying valve entity %s", self, self._entity_id) - self._percent_open = 0 - if self.is_device_active: + # Issue 341 + is_active = self.is_device_active + self._percent_open = self.cap_sent_value(0) + if is_active: await self.send_percent_open() async def turn_on(self): """Nothing to do for Valve because it cannot be turned off""" + self.set_valve_open_percent() async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool: """Set the HVACmode. Returns true if something have change""" - if hvac_mode == HVACMode.OFF: + if hvac_mode == HVACMode.OFF and self.is_device_active: await self.turn_off() + if hvac_mode != HVACMode.OFF and not self.is_device_active: + await self.turn_on() + if self._hvac_mode != hvac_mode: self._hvac_mode = hvac_mode return True diff --git a/custom_components/versatile_thermostat/vtherm_api.py b/custom_components/versatile_thermostat/vtherm_api.py index 964e790..0489840 100644 --- a/custom_components/versatile_thermostat/vtherm_api.py +++ b/custom_components/versatile_thermostat/vtherm_api.py @@ -7,6 +7,7 @@ from .const import ( DOMAIN, CONF_AUTO_REGULATION_EXPERT, CONF_SHORT_EMA_PARAMS, + CONF_SAFETY_MODE, CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_CENTRAL_CONFIG, ) @@ -46,6 +47,10 @@ class VersatileThermostatAPI(dict): super().__init__() self._expert_params = None self._short_ema_params = None + self._safety_mode = None + self._central_boiler_entity = None + self._threshold_number_entity = None + self._nb_active_number_entity = None def find_central_configuration(self): """Search for a central configuration""" @@ -87,6 +92,30 @@ class VersatileThermostatAPI(dict): if self._short_ema_params: _LOGGER.debug("We have found short ema params %s", self._short_ema_params) + self._safety_mode = config.get(CONF_SAFETY_MODE) + if self._safety_mode: + _LOGGER.debug("We have found safet_mode params %s", self._safety_mode) + + def register_central_boiler(self, central_boiler_entity): + """Register the central boiler entity. This is used by the CentralBoilerBinarySensor + class to register itself at creation""" + self._central_boiler_entity = central_boiler_entity + + def register_central_boiler_activation_number_threshold( + self, threshold_number_entity + ): + """register the two number entities needed for boiler activation""" + self._threshold_number_entity = threshold_number_entity + + def register_nb_device_active_boiler(self, nb_active_number_entity): + """register the two number entities needed for boiler activation""" + self._nb_active_number_entity = nb_active_number_entity + + async def reload_central_boiler_entities_list(self): + """Reload the central boiler list of entities if a central boiler is used""" + if self._nb_active_number_entity is not None: + await self._nb_active_number_entity.listen_vtherms_entities() + @property def self_regulation_expert(self): """Get the self regulation params""" @@ -94,9 +123,48 @@ class VersatileThermostatAPI(dict): @property def short_ema_params(self): - """Get the self regulation params""" + """Get the short EMA params in expert mode""" return self._short_ema_params + @property + def safety_mode(self): + """Get the safety_mode params""" + return self._safety_mode + + @property + def central_boiler_entity(self): + """Get the central boiler binary_sensor entity""" + return self._central_boiler_entity + + @property + def nb_active_device_for_boiler(self): + """Returns the number of active VTherm which have an + influence on boiler""" + if self._nb_active_number_entity is None: + return None + else: + return self._nb_active_number_entity.native_value + + @property + def nb_active_device_for_boiler_entity(self): + """Returns the number of active VTherm entity which have an + influence on boiler""" + return self._nb_active_number_entity + + @property + def nb_active_device_for_boiler_threshold_entity(self): + """Returns the number of active VTherm entity which have an + influence on boiler""" + return self._threshold_number_entity + + @property + def nb_active_device_for_boiler_threshold(self): + """Returns the number of active VTherm entity which have an + influence on boiler""" + if self._threshold_number_entity is None: + return None + return int(self._threshold_number_entity.native_value) + @property def hass(self): """Get the HomeAssistant object""" diff --git a/images/config-central-boiler-1.png b/images/config-central-boiler-1.png new file mode 100644 index 0000000000000000000000000000000000000000..22f54c5e71cc6668bdd22a7cce3b26167023a121 GIT binary patch literal 26165 zcmeFZg;!hO*7pmAB5m*%ZP8*at_1>=LZP^|xD}^3MS_>&8rIπ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 bJN_$>!TkH0LM;||rw~P1Rhg3K#zFrNL;;oJ literal 0 HcmV?d00001 diff --git a/images/config-central-boiler-2.png b/images/config-central-boiler-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1de1b5222a945920847a24036ccbd7ee64a6069b GIT binary patch literal 87269 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%0D*5hb70GE zEdP~xgHNkU$oLP(8wvepz?*=W1Bvw*rd3scm=6Z`2V~Tgz=1q-o$x#?DdL->kVExu?rY%Jh=kkeuyjs(#(Q#*S;)%WYDf6OJAQ9&N{_J_L8*|7dolKou#?WY3A<2rG011CwN`roCooR#11 zS-3X3h&puWik9OkTma42yFt*ed+M6z;~+DBdr@(_;WOijic2y{{8cUGFntT@HKXbM z38&MbA$z#;uA5M-rCVc7{p034tE9h6&T%UB5C;4~)l; zTlU�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

%;#t zmrvH%qjjDH%{+zhBi#-5J|u-xei++z^=N5UKXwU<@4jbZq>%R?fPjT6V|l$iQsj!~ z_CNQir^ng86iv4;u03yTV9&iSU^-@h$Fp&64{Zyh`^L$y?>Q9yNVHa9dbYgK_JzEI zWT>l@PV`}GKX-^{Jo5o1Gb=UC%<^)p_U!>FS=R;6Bk&jHnIq!2Jxv%E_gqb)NA3YL zET0EB;SFiuLut9A%grX)LLlFW8FPbFOb~d=W8!w~Xtx>8g4rJ%msX(tv7FDeJKoJV!j z(~lM9CTdpP)%h(a3uecH0j>dk2Ph@)61G1>(f1pw`Qb8*4{f*D9_SaL#Cy!7)UXcOgr8ga~Uz2r28OWF>f0D)M2He@zP0M z;~88`={*jG@Y6^ejHDZX>d3K)ttW8F_ z#}@5dmL|b`xG&%p+8QD16(Ox}1o0rY)7t8dj3b*#yiAK<8$~=G%Tic;uaFX&@sNRJ zg*@(k;}CFOWz(DS6;W(zwZ{eVJfQ{4Pt&Jp!J#$$4U)l(-6@~9dNzR<9cLw~!k2*@ zm&jQ_YXTCU!VjbVRg+()!)`+XFv77j{H2=w)Nq}&wIlEm>-MvvRH zGaCp7JUYhrA{kYO7C}A|kNoKP(6;8Yp}vR<=Lf;$9e5UR0*E$h*8yNdlTDsXJB z`OUP%-Ryo*b+ZAKpjaApU~1ATn@%4krJ|rO%}_8S+qELm)txJR&vOu@0uCv6OVzHu zfhAILE1haMHZJED24}h%naEWEe^Jw zElqkoVeenUQ-&bJj{LLn^$O&pfwGKIEk6+kDZ{9rb{_#*Mc%tu+eex{r4NyQZD9ma#dFC}j5r8JaI6bY>iw{BdduTE2?!mX&y zr;EG|Hsn9d_u~yLk!zk~fo1TB{8in!-!lK9p_ojm3GQ)Bj3;o5gt|{}TnJKj+nO?czUqB>rUgw%mg^s_IVf{?F1oK z8@BP56c>!-_i+2!SmAZ=fL{1KCB%u!u!)Zmhdp-V@Swh|i_N+E#t$pqpVEEL7XV8! zWjNmcO{6@i#=G1_%SHM)IGm@RG9a>^|IwEV&oH+t>_MnOk-hQ$SDCOEx-v^2W#$d` z0G<)B*Ct33R?n@SNL|s*n|nRh?_n)MC7MjlFuU{JI-x(~O6a!$7ddoucqMJ_EC}Rw z1Ve)7qi^6Hww(zDcMHoQ(?o)jU9_!k*Zk^305!MZIl9ddZx`#>-8>9vo1N*W52su6 zL@&-@httkFKV8pQaKgP6(=KWT*w&x(*RVQ=?Y59On(bN zk9y2>v&7SJbb7qlagFc<4m@Zfh#_PcXs-J$t|G-^Dp zw!rRqrRyHQ$X#Zr1ix=#T#otNr?#_OOrMS!+W*RkrE$9M36iOH65GP!PcsXhhk%x} zs&z33uV?u^H_}a(+4kO5_zk{X*^GyS<7QbokLGmxJ(^D|lPPkZkh_*-dg@+l8z8@8 zL!0bbW%y;XyiF=@YeIZ25AjcHrc^hZolN?NR!c{2O&%XMd4DjeqSJ#SR7vq|JS=hoAi(3i)fe1x=t%*pY+hDlX>?zsSx^ z6djKhJn1ZkFWi(h9h^U?t?Iz{uvyhB>euRQNvRqr2r66YnCb}-@M3gM?D1^;BF^|? z)#(lMmI@4=i!>}lzehE9Er3lXdoS3{@x;RB=1-Bd^FheE| zBx`q+F^k&tGs0*LMfas-$4hJ%Zw4wbKC&k>Ln&YQ4mVkN^xQ zqFLoFUAu&cy_X7mw>a4AXDLN}$ z*3R6U1_!&^67m<)i+1Fvx4+4BZ+1&1dN{u@BMjwI=^s^4Z8IyjanjPk%kS~Xt%`B!m*)fviz zLDuH`^nZybPHkW|^%1Eb_ivs+5SZ=BZ^!<{yxx7pT6#U*`uzCsJnR4r%-VxezWt@e zV#UDvqMgj{{*BlGVHx7$;x|AT#`FIO!zjdiWN+Vv(ndl>$$#&af307FN|DhK#Gy>- b9iE+MXSK1Jh`uv)4t(8I(^1V;w($L56Z_lv literal 0 HcmV?d00001 diff --git a/images/dev-tools-turnon-boiler-1.png b/images/dev-tools-turnon-boiler-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5360998f4c5ff5949fa708f32ac097de3c371269 GIT binary patch literal 48296 zcmeFZby$?$6E_Yh#v+1`APPztq=1srCw(zPHcARwVgEnN#N-MN$^CEX1o z-3`0Md)BAEkMdr>e}8|xUKg^fH^O?^aKY3YkLI^no2M32x;+d!-4$fH= z4i0Yhg>&F94l54n;Ejrjh={y|hzPa3jnzvNb3+^)`VST%l~PY|Nxpg@43*#c>A%sq zv0K~NYoK_ABa&R8WT{PAzEaaFm%-TB)Y9_Zu~%(=czSAh!oW3-BK1i{8$NC(*ISQb zsHmvsZVDQ|qKB(k<1~Csq@xvs*=`XC&fVeCEX(gfq#H7m2g~_+cN`R&s(ufzxS8h= ztGYKLFuwAk&Ey1TVwQy-I7#Nc#%>6m&9tjQE}R=gznwPodAU)Mry7y=*71Hjms7`g zyEjA=0vV#x`_hBiv!9+$QA`P=qvMXgHq`s0H{R^PR1qAZpB6r@(ny3nn11{2!Tq7b zHNqNK{rk!Jp0aWGc`mt$KJB6pxe}N1wuKsZD#1LoP33D%p_%Cc&AXHzy%I{Tqfcf? zD&pblYxqu-lg3abyBQ`0jWz{s2tLKAnrFXOk6Rpm8r9+e;z03X8ya_TZL)0l-1J4g zALK&zlR(fahR-El%E;g_g4Y*taD7d1&VpCC;D;Ri;NYD3=!0_}{Qd*{h$i6uef2CV z;mqH!ajUT(e5@oQApw3X>Dw3@TG|?0*_ry&MSw>Qm?%HDdoCl*t8Zn&qHAEKXUO7Y zVU4{7hu?`8ytFX1)1`K@Ft@bjbrQJ!^Ao(_HTKV}x2b=A#Li6M_H!9|Y7r|NLuxLT z2P_Y63qq)=srhXTUh*o6iv4vvcqee%*v`(HmzCAg(UHZGoyE$=i1i^44-e}DHdZz^ zX7CASTW3oyeFleuh-B$1czRrevO0o>wjP9^2`QU zMyg?z7Go8CTvp@ed=AU+tQsGcFjAUPcx#0*sXFKmlvs>N3uRBeI2t3=!v z$H6^IEd0xVwHHYk6u*$FlCo-#N`^5Sk}|2-O$}7oFMa&Oozzcd0h;&(pMbeEkb=A7 zQ=qHo@57;fE$l#GT$E^&!D-fCrpd_EIFi<#shBE>i0{i(9cho{E9~aR+&*O@)WoH) zeIA*XCHv?5Ut>018psWHooFHqI7db=ac3RkzfOM2c&X{|mEWf7-8WQjL9&h1Cq~S! zEBco>tf+HaslMbh5c++FV8R8r6L@CA6jn+n+!5ax*_m&;m~^|yD93+O+hn(Wvi#<> zS-oasG=#QfvGr)qr+&$jj$+G6{$^J%_^UR4YUr-UiynjwOJR$2aRdR1gZ;QaTf?Fw zPM}$k8!hsSz<<7_R>EsReo|wolq@Rm%(PqR2I?vi-7|DOefUt}sIY=e(P#pcWcdAN zO>MuHe1YHW0w|({V1DFgnAW{v&3S0n^M^iMw2Y0rr;T`sCC_!wQpq9A*fuf3K~FGO zbYYP4G-+4jvqh_Ui{vf~K1QEvKh$YwFQ9BB;nSy?>W@gAMAaY#Tk2D$zz` zNziH157EbatIZ?U=J}hw0`b{MzV2j7OX0bEnh1g}61&V?=RE)96tOok;6BNX`8fON zG?DPo%JWBwGiq0rEu=lq*HcM(_QAkGM(4DT9*DjG$}PdJ!gAX*Omkq5tonRvUZ$wu zA1v!Ffdo^Lr`_c&BHSR~;_s&LsXjNxQi5W9o~g>aOs7Sj6hNIj>U| zK?n&DSjvO$i>D1y8>>Gl$2Yc*PZ9Mqf1r8A>kDU3Gb&cBj#4Y@%1^VIyHA038Q$BS zI-Uq%Y@6LT6p8;BQZ^1 z`OdyPT@kZ^oLFdvV#CXCk-GX{-{-H4lr|Q76b1GMqbVNhiH4*7`ng+A8_H&m@TMfP zz$p-#sZtDF;K*RU{>stbMCsA)NXKxI$<@_jgXWJaIhs67Dp`>lh!E-4mQM`R?n}kF zi&zuH?gxrWN*3UcCAM?2rNQA5%o<#yzH3#=8H$$9Y)s$WCJx0;k$aP~C%@7{>I1XZ zaORSiZR8p??x>^v>0p&|8$&w9OciFcp@LL`VlK;3;&crti`-(e}C0Zz#;=T-}MmV?C>JzqG zu7~>@CHjp)F*R3jUO7$MCV0+>ZmGIY^*Q4xSB*5;SaaYjxmM$@cAgrbPbinSy0ktl+ni$_*9-8)%UVILz zAbB0@w%KfXqL3_BJ{2|`;d7b|*WhnT&!;YC*1b7DQtNppTP%>=Val6K9p$nkEkZ|ZRnM1i%>(4QahOji%ycB2AF^LA(Q<&!|DjL{~sqZw36{zZP2QV8J)}`i$m-z8Vh?GHLte!7f6R-cIqhj?`LHUiu#N6QSdtk56zdWhj{YG*@sS zN8RyO8&XnXxA5JIiBPKd%Pu5`qay-YQlu?%b4TMb)S!n8SVZBSphxxiMjuALMRRvR zZB3r1&^8Gz=Y2Lm*qpJvp8Dj$PWJSo@Bd{w{@&F`>+Ukwb9ssix(mp!E$qXEkJFQb z3c#={bp!5*2iYYKAfcIAU@JqkuzIV(m!!5{nQWrH3CJp@-t#)*>yQ_uJ6CgjQ4qO9 z8fQX*uSZDEVLd^dF}a);?`}h?Efs$M)sbmC>n)G|EH&U>bF$>~SWk03!oof3hN)q! zI^%)MQYpc=6XlJAX{}WIlvLoCg^{h5oYAWjmG3k<`{cM^c!)m`}c{vG;Yr|SpDucK@h^>tt2M1mE4m=@T0tw z32jn9^1JQR^W#AZFAE)7b2~a`WvbBk7aDcgmHHK1={}d8hhAT08+iAd!5&Dy=t_EO zDY*H(E&`7=yC-HQSv$>6f%$z5JnVu07LMV2I3%cZq zsxFR^_P?8pFbiZqq4m5DZ&;R!)6*q(CqI0e_3j-KCMCqTBM>~cZ{A1WlM26tHfUYq zvKStu@x7@B7DQ|KF@b*Cc`CO&`=$Oicg#vDhYodTy272Mu4LbZp-3T*Y7jeqGgO@n>+9&9X^kiagTR;L{5;_NQZ~2dN~Puwf6R` z@bh_B<4m2y)Oj_}HMkDTKKzjfm+$F~9wtD`sEV|8?tk;={59!K_- z%LT3Gbzb5vDaam6aXQ~$?Z^5>^cEM9dm`g|X2*_BZ zdwjiU@%6!KmM%OT)DJGcc$IdLfVFBeJkw?=r*gFbj5}?Jr9T?@?1|a-SPC=yM711w zDczjI&DwYD$Z(Gtup718i3uI9zag-}?5;$AeVUl))#P`Tu42Y0=9LddEU1yn zWAtSk+>lwL1o(~aRH?Be*+O=!U(6fw>2>^@*7M5l>B&@x$glvy-79XfVS-yN)y}lr z-JM)QRWc%D4lBck9>)hp%4xjzi_aT_ZdnW!7=kUXj6>vWOBj>UEnd4ku=!5lpQ|}J zu3Qp%yh6&7TC5C{%Jby2Mz8(>l|~5 zA9tOj5VOA?U zSWw&D>o0A(4_YPqjK(GA90qlLhi2QN#~z9YlFvxeoAqZc1Z7i;c?>C^%W&r$<>*dV z$yP4`+x37iI%1~Cq(>7c?X?B)=~G+pcD^UPxr}wud;wSPh^1tRZeh~N8TnS@CHeU5 z7k{+tOeq@#?7V+!nYI-%H*vgMYTcCK&m+J$7w4KIfSyn0GH4>Mob)1){w)2bBkBpO zM=-R_zKwIBthU$p1&E0rYSo|@CTeO{#!-Bm>s7zx2=q93b+t^_B@I>Ap*T(T5g~MQ z+lnEHJgM8df`cU2=!Lro2YlV2ZyB=Cv(Z+gFM(HyCphOOZ#NUBTJb^(3WZ$Oc0VvI zg^g9%TQq;X$Kq50B&0Q&g1m`;%P1aJLXgh!UXfHbLes{Nz|n0mfmmGRn|hJ)RFHE6 zscKoG74p5JlGDy|VMj&nk4_PyVhXFOu4>FG9~sNSA?br`wZgC7Pta(ETS`-n*o;~C zLy2k#qE>Q_`aHe?q}}c^h(2;mjrZ556y|i5o`xW^5OVRW?cyHu!|nWA(%g3~m-@5A z<9a@bQ<}8FISI~dLfS~aFWkiJEX}nmL3NPllpd*6IlUr7EZn4%i(3vl_{QEbxI9#- zm6z_C!ALho*G7a`XDZN>{!BsoeP5CTTAINoLomPeo3NFh!grBrltCh8U9EWuR9`SYQ84RJrQ1yS#ujCm}vSOwRVm3E5jg zH&TJ_DpRTrhFjwz45b-io|IMGkN+XSTBhSLxSSYr*J0e?sc(SA~L!k z>#LpU!UhB(tt9^R{5SX<%qwKqYEG&NhDpBe>o4wqkb&CR^f=QS_wb-VAfDI>mQ&#?zd{L z{1K?h`ysyGyvCjd1sY2f;bY}<`;epalcs;d_eM*AV=N)BF!~l5`BJmwk zv0GkHh&4QjG#j>FcRmLqBQk{zZ+)^%26bJS*+eC*G_sXQqjnYcQ{uN{4vBl-qm}U^ zg@|rsRLJ5g7x~{|4xn4_eh7VBYTCzWHe8f}>0%?)hk9!kg@41Oxu&@$uqvW&Z&=6nc^He9OT0gYx3+p3!juj= z573eZ4rw{h&YI($+{mrxu@1u~4q!*Cgk(8y&3b?48BqIp+$p-qCcpkkOO_Wp=9|UU zW`$V4zn|?*4oPn*_*Nvk8b2%Blh2u^Q52pYTTQBjt76deN;!5uIGI@0w%p`;niuqj z@v|%|mue0UWnJUW$8$(S^&0)g%QZZ|Di!ihm>)YKNYg#K9X7W3bN6;Q3dW-tKXI&u z`5+Fa0_wDPvVGJn;Bpd)Z)XcOLxaq69uU6jy}N~MQfLUL{W?&($uR2lH4G(E8~9mk7;<5wr(>R^0H5YmdDYK!%m-SWqDaqaC_@{%3;|=AKI)vnN)u? z;t9S1yhHNpLMHqhH{njtx};Vxyq*tsC&6Q_l;F7k7|GvrcON7>8ROnwQ*O{MEPxBH5=Jpjo(%94x82Lc_21+Ri+>pk@rA>4?iA~wcQo~ z$I;m;To|{%2*qWbzr^KTyr{F4%z#6{h5rJrcs@43$9Zvl>5#){3mKQQw~Y4_+odj=46Zz+M8Emob9+aj-c|kfZtgJaRZT@f6j_+ zJ;ShbL!tD9ak`aj@7oP{r_M{E<9+?I0+u+z?h)E#3|+Lx@mBmQ#Up$3wlz|7zd;j9 z7O2&j%ThLi^@9BKR7p0O=efGMbqEVzLW)_uQsdN&+-fw^HEz2t62e)GLK58RU>&5( z{l$9`(|^Nz`oxxCMdBh%LpKZOp7u?cy@9p~eCZjKmx9~!TZvNlLuLEash}=+P;MvV zu;?t@ReH(m^WKj%$vk5jT|Z>Y;oU8l`1gKo*h%bssg&+BWs~??EJn$mj3Ha0#K%1A zl^t24G<|2N0%vUJFp5PtUyjI%q4*6xCYGXBnZ06$X_^7Bz^GBsPLsv$39_b6rp?>V z!N28H7pOyA@yf5bkVf*LY}QI%8T_8n;doZj0`zEL^}^h#c%&f!!tP(~#yu^A)V>OU zFf+%uTc-j~1po*OzqoJ;RwGQEiv_}p`RWf&1Huf{01(#t*6tU>{P{Ly01Jc--4oi?5yoInOu4&slfP7~jK z0%8}#sF2I2jb{@sykQXAiBF~(l9e-m{`W*#I05Hk)iHXB$DLDx0A(}%!&Wok%xNWq z9|X8Q+_M2AUnEYudKU`-zPmPZ+BITOBU0E^SEZF+;JR+q`d+&#_SZUYVa z8Rs*fcCDHiD3GkwFI4mtgL@_lEb0n#$k{U`* zyLRvdJXL3uR`!om6ia=@^Shtbn$KlP^2=02%&}Te4=l~3d!qxnOHChWJwY7j><0gzBGLOeWt=wSL8`m^FB(c zQmVC)T2RykQ=ua%^vcy2`m)s6`8K{>#nzV!LD*it#HwDDUTo6ywOtTnE+rw6zq2$j z;zLOBy?_CLyVz7I$HIR3PB4ErHoYt>>1YnT%)c(ocf3Ds@%8-;HNmW${Ks$3l~_;J zzr}Q;i~$TC>$+NwEput0T)gV0PvYVHuU^95PvobPZ zS+yYsKo8yCB?w_=5jNL9KHQQUDY4LSil(qEt6QzcSYn}TCSSAObOk3ZG1lt+?*vQ3 z#hD}07%IEY$0`K|&FPf@i`n|do}AOV;?27jC*Vpu!x1EiHtvyo32&c@b6tHUqEl)) z29IL-K2A4$1R3ur<733309dF{o6sL!EE11@Xjq{PC)_dFFtGQpuBm4zrj8;UM_>7( z*6Rtn912ek=Y$+Uvgk9AIw0qCNBbAYMIwo-lTBLXq*I3k~DSiHw^IX3Fr{ShiR9CV9s`Cu;IkyPUYSt4T&7AwLxaF1nl4dPpUwoFW3};I7d{EK`*xT3?pF#c1Ek`8 zm)x!hu}Ymf%newsSNS%`7GHS~>1k;NQwl5}Zgn_%cyJ)liN2IJsR%4kRC$R?a8$k6 z)DUcL(mhWBT&~p@-=AsND+iBt{$+cTPd}^Y4HX;&^-R-Tsv3);=B46sUp*ee$ZZ}J z7Upk_V09ECk4t+XLe=8Bw|c9ZD=Y;I-|tR%c&G+)8nxfd)J9cMbf>LA7gAc1qS%cp zh)yOvPEe@`#IS<+t(li6xzF>HIIYGSu$8?zXbl z?2q*v0)!{O*kYs~pvC$HW7RNM_zr}%(tc@{Qc@s>F2>p`lfGMw*F&oYk0gw~rI|aEuDL-<<`OTo6a06K+ix@QCU3+(LST1RV&R&)k8Z>`q2f#k{bBQ?O ztu=?6Qe*}I!D@rPV3<+bw+F%a2p|=V67#|)2i#QPe3H0c?wN|qOSv@;TP{b_R43MT zk@a}q5LL}r-UMp2GVmWkX*61JD(Ob$^yGEvx^@7G?q(<}3MUIc5t;Ah2p-JLtzN{t ziEm1#?%OUSGneWI}Vv=%_g%*b;x zwp6UyJdWKQ&BM-&19)_`cy@w!AtiKl)PTz4Fkj=^TDi$I78-QfS)8dNahd0xS^69* z2tyA7=ek*HIi>Nyfn#Z2LCz0Ye-Y3~d)-oBo|i9U5UnZV?(5 z9>s(DsJ%eyj0HSKfL=)zwFc!o2M=RGR!(Rd{R8<2y8uJ3#lxkJtd1Gj~jid+~@n5?kh|tCq}C0 zT9B^7c882CkD#hrGxhYSF0@mJ%wtJV|C6o@XLKR4n}@q8y-U?!*hgx1mlIuoPz$vI z^`IuJm(ko-4xgn+;S9tST;`D)Y1Xm>8P-y|L6l`gg8Q{Hb|(`j#{(A0=MO5Da%^#f z>a(j8%g2x^*^Bk!W3B>c$g9&3m^=EhR$_ZhnT`D$dIEeK*O|7J64DW!QhU#6KIVwa z?8(xE-sF@Eub1M2UVcrNTm&YwR@Fn5BTE%G#)sN8-P%BjK8w2l zJi;9{+n_X)Y_+I^n{u4503T0!O5jMvF&C#^b+muHANb!GP`>P}axAG_&-|UE`K>gn z+3OwdJoz#uzS`&qIj<#k-vO;@P&Gl@QV>&~NH0Hb0p&1%v{FyrLm6~-=WQk^1H=e` z*K^3CL+6Y|F;ea>IHL)PQ^rDZ`<+LhI*A69=~H|~Nwvva=AymM%h2XbK88L<&+-dU zSL4^1Jp5qGx7{f+*7*cq+Z0*SohJqGGN>teLB=BMQ98o~fBRU=H?>s5I(6@>Bc$Z{N0Y_O>-g?hgAfl|2D3sNDc{0>RY6@ z^3ilOQe6+$nlwR+nn2^khas9*N!#hOE|fS)t+`#8+as(Vh>?NEOK)i&AatW4tsG|+ zJ%hE+4`5cd`99e8bXLB2balCLFBM0+??tuAe6ss@lZx=>P`b1s<;1A^m~HrK@DrJ_ z>B)R${8V|CxbQIjw1Jv-<}3O;q*XQ5Ym;m{X~bOfS&4yy_qQ-&B&(X=9}scj6^9f&x`X!rR$bvrju{l)Vd zxNky}&D=S)#KmOxbf)4Nj}CVs!kkp$w4XcD4QB>#?8Lr%B?dD=p_MMo&Y2LbCL7T( z$v8lIm9@w&p=9|tM7iytTdhF48RVu&nlCQ2UhRI{)0k=QU-kJISBM8DmI&rnW+hwI z?xd;aWt2#?gIrYD=>W9`Zax5~tN=LP7Da!dGi4U;E2Zn_MGJGP0o4ah>hQ_L?lDb~ zWs(Bpr1kg1Sk#<2nZ}o#C4*RU%Nzg9+xO^q#oAL|J_RN$%ut*R4;k|ue+HNAjUOe*9z5R7z zXL`Uyo0U*i87IR?g7i7OQ93MWg9D29)y0yNU(xohiS|Q6DQILW@_>MI-x~oAJ?NTD;UOD zIK)4CapOOM?`u!JpA}Yo_o;7|$(@ASc$`V5Ki9U$;Ludv14JN|!qQjAYCws_gqA_> z$0&&M7Dh?o3*>f}bhHrq3mW%14=QLiRp0f32%StDzqHFCA3mGs0?^kL^irGdaRVF@VSgBTR ze5HsD&C!^sF9Y#&`iMA7FWDXXo4tj^*$b@~#oo@o>p{s!rZ%tbI7(ukavhCz7p5f! zePB*@-(LBMo|VKe=J;>k?pc2?VKK{qQ0hIMaO>r<+ll4HyD4vH2RE-48Z|yn+R4y$ zfKS2zo1;sG&LRpK#o_0a*K!Zl4)si-mW1jxTP|uqWkyP^f`^NrJb}*5?{}$J%^@JX zZELC3dYn)fliO(H&(!)W(-CHoNFPJQK+Oc#u$A`%$o1s5S7ARVnH(!;tycB54yK3d zFJFwy(W>O?r+pZa`LUhod{FU+elvGgZ<7sa%lIhw?fZtQYtI`Ee({FF<7A!OYzXeh zM_1+er&w;IOmim<_SDMt+ICPNT6tU1w9A9iiw&H;*RonG)u@se`m85Lf2fabkuO;? z;r;I3Tz1IqWMRhSo$6fU8qyX+Ll69r{3lKCS5;MlZ>_j^wSMQkyn1!1xSf;TGXfnz zX9qnw9=Wa`E5hH}v*cULMESVnzo|C_@fwLDQO8e{mHZ(YO6qja=G$aR`85}OIj@0V zZ1?9|XSrG;nfKS+W<&SIbK$ic<6Ey=+dxwy_&sdb9e;0G;Sot+tvw8~d3x%c6% zjdSb24*=}iceMb7v5;c-I~@Z+ha&(ODTkeZBzVezguC%Cpgzh<`kwaKa%KP=jX1uZ z{uNusLU{l0EC0U)>yM$tR=RaIPg5+8ryDczWmGt;PFybQ+t6Ri&ObL=`bfMehJHOK z{W`a6WsLvz5Li=5kOz<@>EcL7!zuc~S9;x<`C?o(2lBgB(f0z2@J8?2Zv3uw;W`rP zZN|vGu*_2ksE}d+>m^nnY4ZPWO7ahYB|&CkGsxc&RL>{ClA!5ULS(04+a%He*z|JN zDgS>{{BQgXR@wjGv?yOCuOslU5=q!Rkr>qyTUNdycqz{yvM*ilo>W-g?Wg`EZ1^nP zd}kfj#`QBOxJUd`GtI}B8@Tkl%_)RTkruLJA8+|4%l&E7Y$WuG#io4%fT~d9cCdMg z`{kK`jNS8)p>rcrr5^)l&z3zRKiI;1wB*K+Ky5x)+r_n>tXpEd+U@2{7{h@zNa1nf zbhSbR^Yc9F#88@T$#(&Pe}mJWhji-(6P3#eFB#N$H&`dAOkwdS_oZNYwsqI&)r;Oi z)0M|f`!X$ME?d0FCKBpZFR>!8HjYbtfY18VoY`nXqB=95N3j{M=4zG~SPU02T=g{| z`*)mGj0uZ5X(zV9(A;93aJh}iYk^r~3e3x;CR^r5@IbZI;LDJ>+BkuiBPEulP!GT| zYJQj7I25BezlcP@dUKSGnjY~6&fm4%M$q9m0-{Q+1sPqpf_ak!xgcVU}eaYQGrrM(n^it)qkk9O=%mts}5+U@1==~lJ#%jpAz+5*Fn5O}O0qr&;d^t4X z--ZUyQPQ#lPpQIt>zk_3L_MC$squg#4fi|55wB^BJr?7*M?)GRVJ$;}d%J#SLR^3P zAL>H9OIF{qZvM-#bCwql?Wbe&9r)|fY_HhvK23cqATedKNysWNJI_}BRj-A>RA+;EqgE>j4#I3!Zq zD$UM*W{HoKo}$_#ZtVK&oB47xVcjdGEIA}QA( zNy@gI3+e`3Q@$Arn3iAysZaE}*!2LIVTVrWgV`9)M%5W4=~yZ6V(T?XH~S|mOFTM% z;4gl#dwx@PA^q8gb3}yMoas6VmJ?*3wEM^MpD@VVpS0}mss^XIu0X2LDDFp9fw_xF zjhb+c-Nt&uRmVKM`?5X8B)8_cBG&1zVM0$n9taqj2^SP+#(-C|$n3TkiMHWLf!n{_35 z1Kyg{>Jb17vj9uI%b_*26})~a;(NImAZpA~VJ3d$v;@<^_^m4u8--bh7(}8`x_98a z%}e)|o^SuewdEh-Umt(|XpK+xZ!iU4dh#PhhlC@2((L-C2>G2etU^&JA2HKPowVas zWaZ)6y`;^)7hQu~7S95C`DJulK)v1s@N7$jP7ZsHQV@s=7Hkn_q{{gxjY7TDa>Xkj zLdrN8WfW&ZNWoQP*cv_rs9)XYSa4q>QzhFR@XRbHShOmh&$Y*5+2YtnkJ9D0S7ojH z)Q9uUhYA3XS1y{8Ao+QYbh#9i1whLe|45-x@REiI3!mNmiwZ7IZ(C5%dU&G*l$;!v2d9Ec zUrmcS%BID+Aa6PU_)r4FIB1mHq>cdIntJgnAjI0WmYNUgeeeN%NLMF}lTHlarK$iw zr2)8VaIK(6-!C{)_N2+l74q*R6z8MN`ZkD1u20{9ShoIbGw}p{B~ep>PYJkhsQwd@ zkX-UK14D1; zW0|`w>81e3N9}(PZ~%1KiiAwS9|u5xu=gGckZG4!E7!R2UqVpYAdO@KWi^JB?oL5( zeTW;DEw8F&AJzw$^D846N_2eSE-iYJ)%!tYlN6ad}A4V+{wvsNqNwIrFcLc(494#(k%Kw?P@JYfwn*)J@tj$ zOGt6Ao#4swUX4~$$;mNBW1<=+GjPLXo=bK;_-VkkC_I*<{n-gKEti$jg{`qgpTMgg zKHWKmU&l#3iOzLyCUOlrNi!u8!&|owOFOxqsc?{t+^4N8{0;7Y4K9 zRg*b@yyg}?TkLgBCqLzdB+W-F)~a={%ocKfr#u*_1ngo7h>;KV%_nO;S5e5Q!?&A& zw^P*?`S7zKW|B<4$VFcc5n1wpu<%xmN)SnJn)r!0fezcL1_K zm>ev)h{w*8&L~+2cOy}}W&7=+1E5q_f>LOvskH6;g-F(#!-Bw@Jl)vZ2xBveUGqvi z-$P0#Oyoe&(WQprh?zfSX@Ko{`arz&kB@hwPI!GMZq}FKE2N-EQ^?(&b;78~ z&|T{QVtCXFbI+7)A;NvHnW5`!NRsxzn z%QClVFTK*t2q4v40?;LtV3Ff3hgzGjhTy(GAn@gLzE#QAc=%!OQkCT|T>_?*<$ehdP~6cni~DRb7UG3~6fEo%VBt(4dT z6@=B-%QkxI#WysqzCdiXAoIAF)MHo2mE69WyATMtX z=%mTsQng1MeC0Sm(H8DOjge)p`%}KL5or0ah+K{Uj`#L)3(R}AP6rUy6G0TLDCbG) zre$dFO(!2VUb6n5NhAqBh+81z0yF-9id;B>7-BwA@>w#B<;G46>|JVDox}oXjp~mF zjguvH?Rv}uTukBF5KH*1y|pZBcBh*rpopo!ExtP^cP^Dp2if+?Qo*(roCf237O@O~ z92*H+s|>;$Y+u?RO<&@Ufbzo{D+B9O&Qm(oLZ6K}O$Se+2Y!y!;r96dBqCu@^$owg91+yw0<9llQP^T|4thgzB1_InNu-Gd$S)~XO z&bY_4{)@u(*2E)^q6n%3t9@93j`8J8KmzCky#`B+ksK$gc^PntFd2w#FNY%(6wpV{ zZr81RJW$^XJUA%-IF#!{kKANb4=9#x<#g*(jR|9fwzTq`|&G>W|_I?4niUiZV$$^A~&` z8O7R%V|<##DV^_TGzKl-=fh*Ybw_0~Y`mfgmUQD_YcF&Lvv=&+*~uEP=a?nWRItgU z?t#stnBzW(bjTOkzPRx-L8~KuA)D*vYE{nmSLQ@7iWgtCWACHt+V4$gSSIu1+>Mn& zqdiIrp~Z-%v5)9u5T6mqEygJ{lDF>x7CiaYBBQ%un588|Z&CP4w9hPU4?73vp4>b3 zKOqxqfNbEehTA;;Ytxaxj^Cf9Nd}K+IV{roYXMfA9)_V8|{XR-wdawjb% zJdT>_;v;8K{%P1y+F1wyWR4iBuDzi@_I9u(JUTcx|*bq5yH7*`|4ULx7gBp>z?m$ z{X~SQMg!YfJqQW2`j?!t>@p_MNR1)LYmZahroFYgAX1T3R;&$+k6VvKTcC1TQs!iG}i0x4B>edwswp!=h9)uNiSDXqK-&?IA0d>jQwwflj^{%kXk!wp|-#|@Tg0?sj*VzLB1Sef&~dAe^1sRa4kD%QnLJi5wW z>6?v|WMj@H0^a|4iPeeOzHlEBx`ORX1ZNfuVnrbUe7~`?IvI zxP`1U!WgyK7|!>Fh(Vnm2CFJQB6vz?uVf0ZPVJIY&!q`OuIo|eK6!;He+HHu^}tV3 z@a!7ze8L-Z;44k!$?iXYF`#K12+m^I%Phh+eVKvH#))FZ7N`wNO?wHrwWhu0wM8QF zg0N-Kh?NKW#YR#iv`uqSyKs}$2gQIxGXlsAFOXb(fC+&O%uIZs6@mL_{_!-AaiMNI z{Ad^Ma+nHH=EX)U)V$7FZ8h)vwHUK~y;Me0X{c6Oyhn_{dbiAOHyzR2Gpu`;v<_ME za`6r?S(|`GC#-#y{y~^FK+OvQ!+z)!gVOH9xQF^*1zQjCymcWB$_Jp&`5p$i2u*8F zKsBiG!(h7yDibmR0~zqpo{%{Zf)ah(MJ^ECE%jwa5LEnZ%sB#WW8aaDZhdO-cBg-+ zNk0-^)0VC|Ni!|M+M*IAmdp$mn~XHAs}_8Hgm2@4cH?~AGvXt)!9G_U?%_55Ne+~_ zLjNF9^~3F_H=97U(T$+}9DE6-USe(n@-rLHNMw0$IE&UIvUS@9 zBu);C8EJ5ICvme;oU>Q}cD6r(K;bxI`MwBunR_XBsB$1sVREK0H^-*21ut zV*~^*`AdBSYCTbu6lui--7r`AoqCQP@z$D^~T2c0M-&LRfy7U{V}NOsyVs- z%j+p;&U-n->$;LpWP{=Ox5yB(f`<<}+)_5+HJPA63^CWM=mqY~o%h~wVs7$PzB zmnz-QVR*)n0hcq7u8_Q(-$=<06PX6<-w1};wGSPs0dT@YLqHfY6>!}xvZ^`i_f~-3D1C|*h@)C8Qh#{Ej%&iaXJgtw-~Q-j!%4-FZ(!51F#(f%v^U+fE}SaMLMKY zZZo4P#OJvBUbEq}b2>bwFZ5=q^~jioHRa%guGFT0{!)6{ZW6tQk?tivPEO8o%fZX1 z$v*llq<8(RIa|`{^kXb9y~FbQ|3u4w+Beq7L-IjL|5eQVv~yWPIqCDT1EKZaupZFvQ5(mH)70`C?_zff@YCwnC zfceGaa@l$31%wjYs)qLtctADajIoSE=N-Gxb5KAx?g_T4bG=#KZq<|U_zd4lQI9z= zTK6k8Jy$Nk5rt|9mDpEbZ>RucwA&*Tj2@PMTx!SG|`edhOyf+#E z1r>V?14z43n^Jt=-`&XqEl*f-u4Bv3A?g6x^eux-X_2*~Uo5tz+av=@R_h#vlwuY75 zetMPQWa}ILtJ@_YdK?L<=)1U6{o-zhciW@@FAbf`f)bz(oCKk+ui=l#AP4i`TZG7fW_t8_Oc^xB}b{%{??+#9LjD%5m4MeJl%rs zyw9Og$Cu$>vqPO?Tv{5cOUi4+Mu!WoU0*mbIdvYEAS@y@gyMO?y zDL+_bqD1y({`yQynC%>wN%vipePa)*r+f(x3aP8%;>U>(WCwCFIj-{}Jb*ukSvpQl zNqNLKHr)_F<|`6sj9GG4EbalI?&wyQZQs^Xhg!#DV6_&TKblw9MAp{=gYM`E$ZgGa z5UGB9$|w7Sa}jlEC#(5E!BbZsuMR}qJ?K01oXb?bw@C#(Ai`^;O=Y58XR_^4b)8Z$ z~_5{;}%WscEG6qq5t61{P&A@}a{97u~XSNr0ev0&=S7KfElp4VQj zpt4dPFDA1zvXMr016`HZ=tFqZ;*1da%Ya$2lasM>+mf4{xfe$K|xqI5ANEnq%w)Nct2De8`S%sR!N$XR9kIoLmQRzAVvW+GM5%L3zFylsg zogTig-Q!*c}0O2(f(!^x=H;xZ`4PGb+rJG1M;JKCa^O2|s#e zM*|lgL)yLIt_=vItJ+^GYkU@5Dp?YzTp?M#dMm6a`awKj$7}`2Ak@M9n@#HzM6tn# z_Clx(v{DV|{k9$92K;kbp(8uyAm^KDr!$>cPsTeV) z_vAr&jQNg^EPD`vOWGcdq-+?(QYU-L^)cyc~AKIw~{<4WUe-cQkZF9Icr&oSmS0{PDo;^{IsQ7<8qnAeWAQ_ z>qBgIN1)LN0oC~26Jo}S0wvzHoor0@y40Rd2QH!Kb3E@PaQ00-h_}s`2lLw>dn$n% zjn+;8UUY{b<{L`I+aGy9i4&Uno`SnQyZgK@Y!YfHpSN)41ZZ}ZkzYB?4%^3Gp1A#3 z8<3Y_J7FW5P<_ZNyQ=4y^v$Ou3uSORNm647DS(}+_zm`p(F6HMJX*GGoGi6HzD}@& z6Tm&za!NIgmtrZu%WnkDvUe}{vCd$4p+MRJ&Mp{pjU50bs(Ed*%c;iI$RB?F%CRKD zm0s6kOLMErLEAs}B!nppaW&-{| zI4`^@A*w6V{M^Ga+N`Qz62&M8n6EU^yBji(U#q)djGN$s9KZVx|Z=;GJ3Id)Zh=7Qd4pFMK0HWxj_s~O?4kFSCO%Xwgih%Uq zOQeJj0Thwm2@tAuLg+OiNZ*^NPV%Y+fecVM8nhh-DpU> zhL&b`TRGq*S%ODGvHB1!|C6TX#Tzbsn$*TGn;9mM5A}7{Q}Qdd0vPJ#{ZjfR=sYoT z8uKynYh{Hmdop6|68bV8W3uc>+&@T$=5J-h{V6{uQWCf)`q=m&m2-F#w!~1VrIDJ+ z&CljVI6Y7yZ+A(YbxxIx@q_3l8;Ve0PfaPrPpWL6d6~75)`iZ{qJ7T2gyiWj1 zGDgJreg|n8`o%DMT3o78N58jmIru>EwLuD3*h_Iik9%zGp2 zAswq9C?_kO1^a2|Rd-fst^2yW@hIfPI}*hnzL_J{Wak4TxRh?aq5raS(mPB4r@^e& z?oyuNEGsed-Ak#B=QvNn<`C%BJ~Xr>PP6|qi2l>txd#Gw)Hol2D0FZeOoii{H)0-~ zE&>|pXAGIwE+46}Dr=v9m;X6XnZWgV3E;yhP}#)Li|2W9+^1Kac^eqJv)4ztPoj9) zP>2>-OmvATctU;l_VE>5(To|DgHT)zt{!Nr4=nfsXSkhbrnCtEe0wAi0|Xdp*7=Qn z!Oj$nCGioAU7|WeiMY1!Mol4m5Rb)0fr0wIaV=Y_8*`tGqteJK=%kPCc0C0*J}J9< z$*I(fD0HL>BZv2;yiU&2Mde`n%U0!hv6ry%TC7)m)_g`7*N>@;cQdASf>q1?mvRj7#{A{3Bl>lhk$5Mz0pc7yuD?L@f`E-&gEu(Q~hhUJeCmd;HU;MRhg_la^>y9XD!Yq7bK_u;PIQNv6n*E-vNz!y%}Hk0S;!4 zxgWuX7`f7g8TBivbes8^8dd2RWiMGjPGD4kq~>1|#^#tpR|H4sJD1Ls=1b7O$Xk(% zvg4`hefL0HdHLn%c(U6R_RH7|u~4B-MM7?~ytWS2S$g{+IMRs;&aFXzo%5mklQfg= zU=^xAj)HBIqVnsHD7SPmgDKsrt+6425^VA0a09scp}gbbRO@W9xm|ADf@;6BuvPTl zfRu*jrys#&w%?Nj-+a%Wv3;(#o3K`kQ}_R*K0tVTft&zta*eC@YhWYjW_JzkQn?`Hxjs15IC z;R^_^JwpOsuLBp;7bVA}Am4edq~vUIc%TKSFaFr5Pi*O$EgA9Qarm26RaC0CJc+eZ zfG71vIMsdYa}TMONL$@#Ht^?N>vOlcQy1lXRCVHC3vrL6-nghJ(-WM0iJ8newGjy7 zqL6rMt@$Cq5~tbEAy@JX3>jjYxwJoX9;x5ms6-OFf+%tCB!Mgv(k_0AIX|~;mEpck za=~}H@-Yt}_2npFOT#u0>v#RTkoXxO;rp@H2*au?T(Mw+{ zADq%QVf9`u*#oMBnPSq~`Zm^C+zndF8K;y?-xRBB%TO_t!@)}U>Xt#-Lf80ph0`Hq z=P0C@!OOwvSz^zYlzE{Pr~2XplRd^Q2kX7vqjI`6QMs@opzLT3oSf>YC4^pREnXRY zAw6Hv)+(%|>HyT5-X~zUjZfz`i)f?rp7W2~MBVWs2EvvRuL;Z58lTb^6?($siy>c} zg#V~t%SvXN+F7;r_yG`{tOl}5vE@^R*)z)*c;_xoN=J(O1tFQRpdM0UgDJOPwe<>s zLX&t{ILlKWIxJ+b8`agyEMABioZH2Hpg=e^9DXCf!dw%-n|sIw1ycpXgi|Zt?q1-M zp%u|vW>QDQcXN#FbS8Q(#C6N?*DUtM4;y*TNAuqvs2`j{eZP$H3>jC6TC_{9v!0S? zs^(h~-0iPMG>Sc6;TyvO@P&V|5$dD>?qjY*LTqj1M7+ z-baWyb{;EQI8xGs!9b=bBssRB$M3TDtzmlRfSYMzL3_EOUwiU2-@(TrDx7bqL}0q8 z8eO@Vozq%F{PFaLL)ZJrVody~zL$qL2LZ^V%-M%%>0xlbiH1qq#fQoaW@E#uX4h7s z@LN5q3>s8qDc);h?)JO$Jv{ZU4O8EbkG8Clz{*4GbO=1($1i(nhg>RJ{`7udaLkQL z?9Vtsu6l0<2cx=u&{(xiocr)+tsq|N2SbXs`NISC`}>`IM?|)*f$cxAo>Y#EhZhcA zH`mxYq$df4O#xie(CpVSXsfbQk~B!HZHzL~x0=YR$|lfPAGTX}79YCj^a=|1O`mW< zCQp%asl2CY>;@}$`!2qDoQfF&wMccUe_`Hr@DU>aaAQLu<XWrn8^_3 ziY~r&?D69K8PIN#X~;1P$9$^AelFA(lBDv zR0z5880cggJXD%cC5bO@SU&TvkAW5v0@3ndIbWkXeCt*u0647+^R?Q&U$TdC9xzz! zQB)}*gL~lL{UzDjFES>gqv&K*2-A%K5``nj1GTkjcI=cK8{Tbovq#4-6Grd3knu3~R5~Q=ch|G1(uIIkC%mz&h(A=P zs;j%w4;3Bo6|l#ta|>b&ugurSO9mEt`j(Qv9=qQ{N{UjV^b*60+hrrGcLRqF3*~AdbMxMce2N2`dUAd%**~I5F=R5&z;Xe3wGO{gKbI zz6Q(6eQn+K0;}McohVj^rI9!{j3yiU4$%hu(4ALPXtz)2^*vH9MAwM2rzj1)e*B>~F4eit|Rk!6vb3-Qe zlYCU<6SDNAw_Dehq(}RnMqGQEa*UzHcrVV2I6sp{$Kz0pO7))GEfvqz$Ew|D#e#d- zkm$=@pFXT*qcucF(UnFY)T*K!gAY9{Gh2f9r>v>7kFV}CEPM95FsSkv6oP0;hxLZm*h)OqznBn==UnhsMWp zav6qyCM5mU6Q0bbKLaEQLuZfeZh9%_C$lB6i?d1l`TzxeC!;}D-A`Du_|}kK9(0@- zDKV&aoubCo-u6+bdYW*WOYBbkwHx>Qhw8)R25|1NeA2N4k5< zR7I>NC~L4d#!=d`<725yEuu^yOEJ6F$PpeU*|TM-h(^;Hnjt~QcGr4V!H=atUcla< z9C07KV}EC_#8f+X>#=m3Bhp8-_bd|k3L#ls%j(_dD&pS%N$r%IRSez0(WfYTq&Q_J zTD48UvIyH|Wzg2|^QJpLD49bFvRS^DhTMO2>8R47Eqy*a#iCMO?wj5eqckF;*7b98 ztXpNCbkFnCx;l|A4^eJ91&+HfYPmGC%URvW-lu_l+VAJ8RAv|∈#at)2ft`Nb;L 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

  • z?pJGFhB1jwr+d*b6t1VL=cx!Hq`VQC0d#Hfu)0y5TVW28TY%fT5Js4((-Mev3TJRv z;dGSdngKFFEU??A)O})hpWP<8wR;e~fTDkUAF^f{my#d()R>&=lx^Soj2RiX$T}UE z&5-4vX?*BllZH<`4hN#6yt$r9$IXw{iKos&cH3$C^l=QuHRYI!AcuBTh}S94mZdr| zyEi;+?108y0T(#R(z*cdkIL+2^@xKcD@}?wwelRZA89pW+d@zBceCfNppq=?tbn)5 z!%6gPqh1pb(bi{W^713hm14^m1hx&d7pkgBVI3F<7j`4Hzm{PQn5XIqdHgb44w+ok zu%XW2HB!I%s9wD#Nh7($;Y!;!ign+4tj_(;tOl@jhuwO$4wxZ$3$CfW!PF%# z7kD?GE-l@yQk+(fjVs+dGVAPRks3mGMX4}`7}MsNaF z!QF@_ciBn8G$a8WpkVTj`?Mx)jk*hc1(95OCZ^p&m|-bK%? z6&(x>TD2skBZqbKp++nrsN-|5vPwH3UC|?PWu`lZq5DQ9^lFWnWv_3kj4w3K;%$mj zDDC(|skN;I?<-qUHeR*(Nz*5w5f=bl{5=x=Bg76pk`^-MQb2^T+UBalnp&6@o`Z@) z*$S!9iYIVKTPNjEzH!x6{}t7ovgx8h+xya3+1L<@X#m#}e5^^d7No2Yc=NGLRYoS> z2a0B8rA&|NK==D=f?QLKdrtuAH$y)snWT6}3^z7;1%7 zVx|d?F&1O6La%BOv2%Oi#B$(H*VL()cjgKpGQExPysMR~S(53{MOv!bz^oh73LNNg zx{_H0HKXz?(Gk5dtwBya;MML~(XC|QhN`eU*>1jB26WfAB*)rNnT=c@E}uTFg#%F}G6s$8nHauFWcJ~L|*sq512wLBn1)HsV9Zour9_f_-P2>`J;OJ;aZzMFb6rw#!2z z;Y^q}#tzX5fiHGjv7!3f{_`7;kPMaLtc$8xwkqP+L6Tax{59h;?Suh8V*|OvDGPMA zyx#lQaxJe@$t-Qob5oeX|1N_5U3wW`eMi-3o+^EBZHU;hPR^hA%M(&`ty@vcwL#jn z;x=p1)L9s8(gfytI<@lU#_WPZz+Gq8+nu|WG|I~_L0ny8bhz`HUc4PM$Mgx1MUq-q zV@aCy3+{wQ^8T`O)!?0}ldoS`GxO9f7K;`#v-_NbL=F!8yLA;%uVaV<{TI;_V+EWs zPGkamTFr@=BysO^S9}EV(Bw1yVAm866n93W7{ByVIv7!Ep+tr*EeMonuEPwk_9ChO zf>om0mpWTNj>nXGbA3OK6YCdEK^y{kpH%RvLaWabj&@Y6P3sgjo|(^-b%i9B8JRVw zUZLlWZ6dd3T_Jk;=c-)-UA2w+%p3?$b@~FUXn#N-j#oxFFC6les)RvTMaW)wFxSa)GfU)@{OxDiRFUWfH((?wql7^^164guI19)%2p1q;@;}v0n35fyI zBZlr&)nO->vB}-`r(Y3A@aCVp+`gn}JrtO!<;Vy+L%@9NY?$dpTm9ayj5v)>jfRKo z{mQxpd}78mK}5lL2FWA61Sq1w(LprY=IpQ5GKLP)VEKeUox=Xo^dma8q@<5T1)TE6o@zbJSjV>0A} z$Gmy|bgs^$^_3PMt>R{XbESX)YNTVHLXT&?5&OtI;s9q3bNzS}AeWRxzV< zD~quT3yYQsYeNutIo|}mceeG;N>2K>+!LTtzrmjFbMy1|l`3a6mRg*Wq*n4XD%0=g z(q9X(ENz?S)KkW#_O!f6A_T%CpHUcih3RnL&y%ZwiM#~wFQ@6PUAorfv{koy2Bi({qu^AD*mG^M65kbH z^MOnVoPtH~UyYXTAJeELAP*GRA@=aJ1;tIbNISKAk#C~NFBKMcRE15CW@^{#Z#S~T zdEoj(o%&0w!^ZF)nHMdmV+6tLhdpOOvnFW8XZP@MsZ0JOLX&3d;M&XQ*#S|5n*D)K40yO^%48P4M!l6bqIwuA&d#H6#a{NaXe}6T z3Zb}G@5vu8%=Y#pkGI%=DI2b4Qz-oAF68j1aqb2tBE;z4Bo2Ib!azBA4AhKLCePS> zYfTWppnnBG()`zhgJ1TW3;8Z4)Vra60X z)kv(bHt+!CpGgUO0@mjVK)#=3Zsqon2G>I`_A+ z)KG#WCHvD1F+%pm%|{j;czEM*AM-QoR{F3{8*cz5q*=h&4P3#uXY&qpo3ln)_n?6y zHMA(^`X*(0Gs_t3u71-QjTzt4X{+vH@0Nq)CT&AM+$0G1xOH32Gg6#dk>zgi>o>x& z-n2o=1GTlW=tOK?>pL3$w-?+fsp>XsvsQ>y5 zYSAYmYbU?0`O^>A2j_KWE^X`7yrC{g zL{GT$ESeeX53T+`=Q3uTht2 zS}q1d4_o2_!!58qNGgjMhR4RS%XPgM_w9*91w(J5yGg{}NNRfbZPN2BN^!ubbha;6jI6#XUH!+5(GzXcur|$y0@g4@F)9pIg>u?dky5yYmj8E-7)4s9EdcV z7+cU+C;K$O3H`$wR&0a0n9;DVlM^<&7WAc0K3!euRK^KaH^%ZG9vLOY#F3}d1a;DF z{gPTovpBYpnRCZ&nH*T_;g=0Nf8qgeUkBykz@e4t4YQoL6BB)k3s+$Bf4bZK9{_yj zmt1O9=bVJ&7uZ4S3&6IoFRBzfTT)^M8r=Vz()`)RzUVuO51oIe`xD}8#1%vHNv!kV zex-l2QL(*5`G6S3KlDEISAgRGo`krR3m2n0uBzA1F#YWh-%&*}=+7*a{h=WdLAd zy+d%Ol?b)A_Oo~)`42!V=IW*2jWgo7B)d_u-mbTT$Uiyll?V(@Mj}gj(k@GQ+{ zb>MUc=}V&Z%#p^`u7$Lw#u&qJdJG%+PI{i(+ zUu+*q7{@CsE3+jl7VHdXwcnQh#4-^f-mhSjv9Vg^>B0<j#VZ8}NjKf6*3@s>c5Ey6*e_-QTO1=db6V=Qtk6ft<HpW&YfDvHOLf1oi| z+Hc=kyf(bDSyfN6)I2W|#U@)c-=eKfFhb<=+1lDUI=C+J3pbW*n8xc=&AlSzq?lmq+95;|jYm@zfu6HSc#}RK0f756W6ivM2D~zogc&kd%GR(w<*6# zPqmtd6epR>5~2;3=sd2C*+R8kCiyiCTQp2SbnHX=zCFfW-tly4oO8XH9nHHfyT|&L z#!5C%PMk6S%_i?U1`M>0mEKcp2!sX0(f-{_+i*Uj6-KzvH)eF2w3gC*`1rq@14pvgZCzZPq=bdt-Q9)U?+YPb+6dp3 zl#~>{b5Ho*Jwf0ML1#}#7c&n*N9P;=b&>zM&O<9_i+rf}<_K38+3VL2 zJNmyr|8<{M9guW|nSU0|T)Kr+JrchclQx9v{z(9kH*Jbm~;+vC{6@F@>%=t%tv`mbBu zNhcL&jJ;UZH5T6cxRPC61?>^JaQAHce&a!Gy1jlc{<$IhCRF46!_;@{m%JF?C|vkU zafBl6QUA2zW?18!6Wj4ySEpLDr%~2D(~V0z3C;#hn%^!b!4Vbw|*hk(%L4PzqJY@PxlcZzm$l-s)XX+nl-x%QE zwxyo`*ENrwxPI|1{WPwmM52pdOsvP;+`LBejnBV3KXzjCj&Eq_1%4r+qU7Z3TiBMt zOc9c16bmQk5^eUt!tLJ2f8H-}`{~BVSfu&*S3krvJ9g`2x@;$zCP59a{x19X7|_tV zswjNh`EsS(`tZWMu%k{neS)6kW~~iNpNtHnbC0$v_yrw|x+?b{0JP;N%{|Ic(; zCB%TA>;5n@3C!wTGOv%~5i`_BNBct5hk;K2s0_qaMx<%H@@+#qId*X&8@N`-4;c?xQ|7!oBE();f)G-I(unUozOt%>VW4mw=qyNRdOoxmKDg9#~oKAA5Ltc~7;D zC&=8r+mRh&?H`49967REwZtF%cxIi+>1jhZx1!14zxzsFOk`<%d%PHISmXfnp=`P) zK^+})sLQpCLSs_PRmb6!O>)umYFhg~ToQG?OHHU@uH#!E(v;wq?YcPBFXqrUrkKgC z6cFVn2t~t2f!j39)XC0rUm6iQ(S;`->?8RF1hQmdIUkSMW=x{KY`G-O<&-<~vCge| zX0-wRQ!O#@#i7!i3n~fIEiu#BlFqI!W2W#gUw>nH2>M3L82*JF`63rgH2uf!oC_ zt8^P)xpD;q%JSJ7?p0P+9{TjySmi+kZO-Q#Ps%2U`@8nydm8nfNAAzFzHE$mZP|^E zC!GD1B_aa2Jg%TkZg8LwX2HgmUWIj?@9D`l6lc{IweR_vf$dbzEiacbEOW^i%;A2I z9nnl@8FJYRy`|#Ng@NQK+yDCh4)f~Sn&YhZFu~oO#h~mG!x4Mpc^gyuiaP)O`&|P? z@Oi&9VD5YA)RY3xxhkC#713C;;QJIU=TnxM1#ZSxDJz{B#M|4ji#rzj`T1!QPP9d) zmKKXlN0ywNMX-c$e%*n6%F>cwUOq*@sH7cg0qaZVNnGm9O~^LDH!(PCH>5hxmw9#k zKMZ0YiA0hHmxa#-Jcw3Oxmd`^DK~WT?B%fl7Su+!t`Nzf*fEZQO{77)G!>`p42Q`H zK+X|8R?NkR)P1yYsoeg+4Jk2+x@e3?ZEB2lCn=rUdh>|RVxm3-7{lgBVfX3oSi`dY zEd7G4l9G~SLo4>9tPsly5wfquy0na>>@)U{2VnnoY< z6g(W$)4LsOfV{}F_5R;`Mf>KburP~gp{LamBauSJ26par1_Kz$S|W9GP;+qZSKvUb z7JrVAUpw!=m%(#2mgQQozpmZ>^xR2<{S}r{vXGhnqpdN-4bti@k0<{&a&Dhraa!(F zM+g}XgR)m!uXsx%H4{@Q*o(D{r_OQM$|5%l{pndx8WtEa`1)-2*9S}dIvsQ0Mm6Ki zuC(i%C2xXvzNj6k;gX<%WnNw$=FCPd4BD?dEe%)@L2>b3Z3F#YgO@M!<2ne!kqVj5Ro&Ln6}(>sc?*h04ub0-q@^ZVd)MMe-~Bcji%3&cDIPIeIW#ffn~Om2O>n|nqD3$5P0S78Gi7&v zhhY?KfSulxXYqsrSV)M^nY4=i-Fdg=>QjLQnS;|G@3-RI$OHZr2RTD`MMb+?Ww(sF zkT)Tt8&F+iE8&Xu(EA*+UWHfPhh8jd?=p50_cw&45BApFrCishS|Fq2m6S#HKFlYL z(cB*5)(CQukllpfbGm-%&yBZyLOU;&sTlzb!p?MV_7)o_DxK*OdMq=}spgF1)cyg5 zctAgd0M`0*Pj_Qmz;??5L+>{3&uXBfqoY^+Vzb{SCYZ3+DqTBZ3Y7s?V^PJyKBXt+ z=|yS4=##5YaTdu+Qa#>fqS!-QNB-pe}%EwBwV+6=;eoO zQ(`3vl_WRxqW&u2D*V)AwxTyc*~~rQv)UddX0<1`2$fUO_6}1&zqZcAASz!xAkzz% zt2aGLO7!~yIKa{k-gu8>WOChcMw#cp%;Z=4P|DQp>)x|0QiNRZaQ^Y|o41dl&k4xC zS`ECuTYEawxuAObv`J2@A@2pU>bDO=)D0bu>LShvL+V>!W|THSiq)|i&4yS1p@*)+{T7M|Ht7|J$aM+N2O zN1-YV*JS$0?tpLqicMiwO`k;pE7cOTxPBbEorE#-^?H*A#yk$@fF*dZzMKE%hi#KT zC@1+4j-z&LfVI%7iYj|-U=TfwqL4QUqf{YWOe_g4nc?-*U2}AIW8TKo({nM}6ivnR zzxdhE;MA+ro_ugO=aMHy;E;gEj)?_p0fVQ4OL%4Ae!PmjO=9fsa;JEKKhF z;rDT;PMPeDu>-AnvT9*@FJv2?$-q6)(_!HmUIH@76ViA5caQ zj672I`4yU4Tiue{0G~c2%HrrST-MEe5{9O;0;P~nN#3T>x`UA`M2wC=sop!2k(gzy zgVv)EYzk_BXS#}KcN2;edoL+6rblhNi&d03vSDM`Z7V*H9s@)v{ z1Tv_)y1G)_GoRRdR__T@N{q6W&eN@~)k&2qP`+7Y=bjO$xJkzwwws9iS+O#+Dp3ztuQp+|aDG-a&`b+<_G0k2i9i0|iS;k!X z7-s1)QJxOXHj;tU>Rm=F?fPJ&mw;%;mB>P8QqST1kuppd!r5bF6HO1{PFH-+_D_FR z6@QBf+`c+ptBFe`?rfCi6pz6|uFu-LPLZ%H%Gpkczp4WQ5Pa>k?MbiM<uM81QRTSW9M_9Z2<0c0~EO9M)W=Ivg9te4X!myBpn;NE5G&!6)6u(@5uQ z)U|{Imy4;RPPGhTh!Gk0kGshl6G^umE*}8fu2N&B6pO zh}j1;#F*%y6&^E0Vx)u3b*;6@o4Sc_=^q^LT7F7*CtMU--)IQ60#UVPx29qc1LJ|5 zj+{R09jbv(C=@Lw>|7bJ3kK=ZK(UL&1JoAh8Dp8)(Wudo>{O#&z`jg2iD1A+iS+l>r`VvhUd2Umu>8Za+@Hx72$O;xBD5QMkzia+!^?_Ce0vvvlZ4_H7w;r_`~h zVF}`nLlU?caJsfH3vBH*`_){@l+e&nH*AzfdMexwtE1`P^7j0VXSFlT+e65u3hwF8 zQv2Hz2=+F>AI5{a|8jSe%K(2AS^5$Xyi-g6daG0GSmvNe z!e_-(Ew?7s=CVu6L{4agm1HJAX3>rIFj2#6d8|Yd_v%_gQnpqHN~h!Pu{~b%*~LTF z&`sMCeUU-H?@muSb$$}3Q)7{Qv{v>xRt2h$#dUF*U~Ar6XD(2*N(!x-FNyE?IN{?* z1DjA5`>1^a>IPddRx*E&MrBasyEgZ?0Et})OMurv8W<#MB;ql07s@#2o@7@wrmn(x zL7fY`{N>+B%p^UDAMHA?H!i zoO|bYJtx{l>3dH3sxo0Azj??=PwFvs^*94LtzGeA4F!_=#;Y@d_D&0L>bp58!<0Bf zUDxuH;gIMWNm-4ygM}s5y_^ZCB#07BL*uJ)v{#pWH1&Gw9bo<Z9*?@^rN>>p`XIVn8YDzZD^Oa(6n`hl7@}+su|H z#r^Z7h*P{;RV+iyPP0j8<~{_N%@K4DL~MqDD_+`I7Tz8ZxbX*sfg{k9c9aE=$x{{z zgqS+Nk6;}%tU6ya%3Y44@UfZJ5D%yb@t-Tcr>;3ocbA)Z(I&{R6!WO{b{!KPFXvh9 zxp+*pqcl3;ZP1nbKOoULPLnJ3oW;}e$W|fZ-pegZMSoT=&=_C^=WF;|{5LjwO$ofC zu;5!z>TM>%&Q#2>x*~%#e{P&}t0X3}Mx>>~Wgw+jZ zf#+vrKal9thQAn({g&(lrtDj;rO8tpWvexNd-e>fv%zlBr&PLidri?5Fj|zq2KGeP zn`O2LwxPnM=>+s5Eq+z`2@t3}-9mPT-C{AYLqktRkWH5*_5TfF<*L`k5YkevpO!Vq z%Moc2O##OY4Gpo)hvp<<`52xE= z-qpJMYk6*6T$F;wG~Q&}GT4j`_d!qkJgJ^e$Hr~;x~Fr00vzJyuGkk+YLF7`Gz7^yGwZcYoU4u%0eAJEiQnHt{al#Y1BkHg$vJ=H!Qe8lmoHyFin%f1R>z{- zm#*>cx>UsLKXQb>5AB{Lrlfp-|Ni|@CIA}@#QhIo+*-R_`?l{W#JG=g0s#IF;Wfak z0MJ{J&au;YyYPe$f9`z15D*ex)nu2HDBIw%9qczqSPc`^;6K(OaJcXlz($c%TTRAC z;Gf9CS6IAVfkp;2X4i=Qh)3tT4y>J{Z&e{tj1IUrtpxic?(Ou$n+oYH=h;sk(dlUt z2EcORbI$Oi*5g}XJzh7T;5zEFfkQAI;*)bEnEn~K=tBpNH~$_2F7=p0Fg>FU|A)K( zdy=kS;3B<$8+7ypYQ0j=6x%2jJ8E}4q}Y z>4$-8(Ut8TBqCq%bArCUSc`FdK;HL|^Qt$FBvjJL1mYc>K_%^_r}g9)KBL zmvVEL|BILlq}lE3#$A6taXO!G`j>07A-y+(C2BBg3621V)`Nk>M9(3zW8I-6V$ut* zVfdG1Tcbi8t{nyRg#*t3=%@|gAy6VhhKrJO-I>!U&NFAuOgiSO@ZZ08FI^0)VtU%= z!5=1*>n|>FQUuu7#ABR}9}IpOuJB+w#AD@4@T{9hCQr||7x~%zw+kbubBn}Ta~NWC zVVdQ`?P);_wh?xZ22~cqtop_5rd}2=EV4tyO|T3N8+c7X+Ni+x)o@zPals zDmM2jXA`@%ztI%5zbKXu*V6s$TI)_fR3?Hb8NWesJ?B7dG$i)St2n8ovs$pawz<@c z<|aJXFQwa@X=`@q955<2l%PI)y1MJSA?J87Dlauy#LRrk^m>*R!;bdlR1i0`>1Vv` zU_5FUru7$#Y8O-8pd&#LH$S?+Dc%g^kO>y%=FbvXg^X+|i-g=m7e4^txL2iIaKLzt z@TkQ*fHFlK@(zJ=8AHIx_y8%SqiNhjrOy4mX?X&5eJ9#JCl5YY1nV%?C31@n*ZZ@Y z93pQn#IzR^Kl+=6i4Jvj<1y>x!CwEYxeqyaVVyUTtBt(FI~!_7MzL`KEKxLyqI`|7 z*#VNA!9#qDU3_s{a0rObBI4s6zkdCiGq;@h`rOOG*a$O=Bb4Xt9#w$a-#TX>?RoZpTuKC1nvkQtr9AP|!9I zEox6&|GmC`5P_x`>Kdn+r2^<}F{`O}$FSD(G$#6S?_4QuYS~Qkhb(}*Xq^dDlRM*` zJCGx{x9CoE_W6{A;6ki_9TK7~dp8{|ic!S& zso%f#Grv4+UZ%Z4tV2HNlxJM9<}Y%<8>01WAr~eVIv2AYAeF2aJRXFP4v74f9}L^b zZkQh}DA3hwEi$*)kRMSlzZx+O$6Hh|@k@}Tl8oj1BwtkNm>>N@YNW}_UJZ$f zKm(A;HT2#KLn5l;Fg+8h+&0(J+vvAR=m)TA{fRfWOTrCpA z!vjHywV(G@jHTE{?K3W}9{bZ%ssZLir|{OP)U*Mw-0RA|VE_VjK=1gZ7-rW}o;d55 zR{-xSO0L2cG> zP6gd3@-26`?yp7?`$!pD!*&}`vsj*bUA=F1yrTYac7U17NN}{cd~n-RyZzhd<{*Ml z)yl{wO8iPIL%uwKRi6jYJXgv*C!IkxIb7Ey?-26FAW2)9%K697NmN}!F61dx z6`I$*A5*F^$eBTQb2xx{*W+f2Q-Bp&=Z79@BmnDSfi!Lq+ z6n98EHhjKeT!q>2f?ActV*|9i*8u-r=8Q$M^`s|#1}WE+%1a99;i{k_uxoee9((B? zkStU8=VrZ8T{goX6eWne4Wl}7Tfno^o!hGAfdOKHY0W-7PSNtOYv-H^=nQ0XGeWB)-HPS8UUv~E8J zgmsF+zSlNO`9ub6lWXlw1OUvsm*gZ>X6Ei!Y)^MH{j?QB$KQ$TB1UjNgT+nJcd0j` z_tz2}QMp&{Tc4P^+qF5LOB@l2)pWMZ7kV_ic%Bt3|hdYLLJF>a9go;Y1AY(Dd9NFItXZ9e0 zE`u6r=0$xmAC1HE5km%+>`moG2b!NRs&2_|)MO>KbUkCyt%y!3X`HH+Y#p^}oXWm5 zO=tMJvuh8UI#NrvG53}wtZs`fhK~vA;xj<$0C+33o<5`3VxNlTfDBwCW7JhxG?yy2 z;sy2dzcWl|`D^q~wQ;6`bMhfuNlC9`#NaR2lwpp12P_=PuP`f;$R`A7NA#+pk*=d@&%XZJO_n4AT+pXx z1jr8{bvTg#0X7_K75&P1&5BN(VO%@?CMDVq+YY8MHwYP*^#MW1@GTaTIqB}3tEeu- z-4cIbZE@D#`=`65p$$KyE&Jfii^tH}m`@KHd<6x?l#@ZyF-qXQnQwvCvx zx->h5)y?Z|*w}u!n)G?9AC#5=!V5_xry_6t*y%9dmc1hFmc`zR;cbVVj)|tdC5sm- zVYzf^V12f0IPBQQ@b;P^F!8giv69YN&`h1h3}29+NQUQ!OM-4vaaa%LcH8({EoP~& z8WClT8?)XBT358Aa0RDZpV_|9qA^jF0oKL8?E%;3WcJFquAI9$A_pqn0}DW9W*a|6 z8ohiMBVbZ*E0GWY0ndOOqr-qO|G}c1OWYVZbBuc3xM%C`i(e1L55B|~rE|3+1~8Ri zT<1YNGuu@FCEmB*%Hc+PMJG>*c3H02ts-HS^%g<*U_K=+qpdyO&U%^ ziD$#72-fw3I&}_k)|!#$wvd9ZOnA{4PMFm|_qQxyHW8pIlPdN%a zjGresvC0`2Rr_m(JTooo;zo0{x5{#Nj4qGf@bdg{OY8@I=Hl1#zU-chZ1_>FT0$Ah z_OD+RT5b*>tmUb4U$%0TZ^%;B)oQvP{$jg^;ri7oy)*=Kmt9#ZCQ`FyH%MAJ1CQ&| zVV8#hAa*E!-}BcyUZcw?3cnh86V7Ha;uEnq7^F!X8x{Oc0H3O6)S$f?WfSlDqg8U6 z1G!i_ZFD=;%3qbud}cIIk%u8eyFEp21{N?bLZ{tp3cZap9uk48k7nAYAvlulmS z`tk${2WI$*<2_FzW5>1$)p_O5eE*d&(}e7U7U5T`z2ROV)}kTkU;H^GHTwYVMB!}q z(e>8}(DlKa*(b%cR}96KhXdiMV0Y$#!6ekkKY9>M%Ih#n_$B-3ZUw45CZ#elRE7(V85;bR&W(I?E@ftwOY* zNdeN}ZOsy>ZVWcJC)d<;d@BQ~u@Sdeyb!C?Q6db*W^e*49$TD09bW1u>5ok^aINfB zpSG0!rx8#fpRxU-@y%tO7WZBRn*p2+#pc67_l6n?*TRN5u&bPnW_Uhiin7svvMn~f%o`0coT;{%} zoU*Jdd=gB5MvI5ghOaTdLOR~QqV!*G1Gr%Y<`Z`CZ8KxkfDyBiwmz-hliv@1yTSf4-bz4V6Fs&gjyqnjP3mW{YCrWsz>1}IC1)i zw@7vXm3nAiFrN8E@ImV*953D+Dxu^K_MCzI`F(@9q)?BEES6|YX?bQC6~@{j>DK)q zgrl#v(x_-;#JI#u*En?k2yhaKSI}0>9vU)20?0AIb-JD_I25G)xMEY%7RCd4e_BF} z=ZBQIa#2cOnY>~?#4c1+`D4h8oa9kz%fvZajWhVe^)}bk-`|hXtqovM$!45Z3QO@o zPPLEdw9(^9`)&&E{I`YNnr+bcT2?>2hxqnhRU-7Ye|+2{!dD$Jb*aygUT5G#K?H%i zhh7}cje(dHG_vvh=x>a>Z?r5jmsYpgxp0*d*PR`1-2M&kE?6*aEl^-4$lgD{rZdpn zTrkY{tAasy6OMRNq*5Gk=7rdcjmHH0U*3kMxE46t`=Ofs*HBw>S18$gNv7W2VXPwWh~ym;K=s|JYw>TEZ(aFWSOX0*ygm4wBdseH&*2x zR#>FLXqkv|-gU$%Z^a$^iJt4t%Zj)0axI$VgOhHB(>6l*6@Kx)ZmLv?Yl2|E1{~wF z$TrYz7-@YF6+M~b{B)_vjq*vzty}kgVgI$*@`elM77NwmvJ07$^W+NC08XJ%qp>2& zfu+dP!g#VKymQF(wGf(p>5qN+_aT%u{j>Uf)p5o`26nN&h5mxfp&NAS&SDOE+QKBw z=7BFua;Fl~+XttE->uPRM*N-{T>iTr#c(*`sUz4&>0;g%?srNoA&*En4Ce& z5HH{GXl$m5bQ#gGM;>0Guuu{#xDYCg87$@(E7?%t{fTE(ySxCd6mt|Td=%sMb0ZZ8 zhl~`(?an4Rj5o0ne9dkHfIkj_6(H1%I8+?48E0;*Gn|ph zG{oRH6*F%lG(B4?L}4wh;p*5LFuU#SPYN=r=L>il$IO@dYPp6s;usEAEr9JoIKnS z+oOu?(@>4<=22_$vokYO#bCnLP|&3AlRD3FZKu9e8UF2p8h#(S7e8btN{-OSc?>0nRh0e z-jAdcV{nK+1VI?Qa~vM97*Q)qOZG5LS!29{)}BiAqTD{z60f4HpB(!8clQYa&+Lfm zcjdZu{O|hEx)|=K#jqfBK%{?IC+=w~ZV;XAD&YmL{qTX$-*PYLf zDNFh^H=Qfnty5qo%Qje7Usyo(66??NLm!3kVs9~pD+@wlbpS3^<$HX%d8s4cs(B&K zePl&mXNS7TIW@>YmG0|JJL?M7QZk|Rh#{2@M1DIBy?0=D-s2ivWH`UqpE=+KUj)tf z-H}|dzm40SZc|yvmm?3&UqO@_PMic%UiKd?MT81ou1YU$beGl@7Q0ykM2oQTyG_!1 zom(5uadYIwYk`iK!TI-j_ZOP2v@QLqR=6Agbhw8Qw)!LAsGmQ7c3ylOF8Sc)%k2jm zUzLfxQhb`57hqp7!s7X;8)|s*bkE@VzK(v6YR)Ub?L1xm^4Hp;R2_< zVCY47wytEvyuYEy5m5dy=Np^YiBF$Do9XN8pO3o^4mUV&ED1U_dCBXT)*H_VFgAFp zujYpr3)EoWCs(X!C`Q`8O!pnM2)=@?y)^YZ+_=T&h|BsKTEJJh2c zVsPABW=g|M5`bvoP(>yO5X)s9++Kf>8#vw54^uTWf!xOp2%G!bla%0NRlbKZvDRS8 zsmmWiLPp8wx8NNOk2!~los^kwk!~+BxcVZBb<@ED%`sFu1n)4?m(Pj6IwCe4TE5jZ zFbZ?5vl&Lv?3p4e0Wn3Z&_f;Jf6 zlcjIbCbRaD<^hG9c9aS^IfJwhyPk&>gx9T`H?`SnK3eu2IGP72b3Fk7e^fd`M~AnhJ6V{nm-vg_*7 zvngR&0JK~Ls56liaxobYYS07XMJi>`b2@H%v#qtYh_G13@05i-@Y-B}857*_zkZps z1L9RNXER%*yC9-aX}IJYo*uwM-Mdao|d21uRVs_9sx z6#WnBDIgUb@?!XUs5Cv^t2x>6h}GBZM)wE!B; zQXq3f5$K~Wv*#O3$Qfzz_sG%?eRqoYF)@z$gr$XqgS|93pgSbKIYfq`pTSPOOtS#(C#Rmr+FCKU z14;(Dj)yeHfmJ-R!K~4ayijgwui2juO}Lz5Ts^WvNUCTs@TI ztHJFt*~-;lz=hhAiO&_$4I%8}t#k}lZA2iWiarRm?FV4By-2fR2fgMWo7;tTn?K6K z9X-*)>ASWo2JY{wM{qS=WkrRS!i8RzbJzJ-dPYQWVLrd$z24ehK1+0F{dE3f>5r5M z-V0|b-m_jYIJ?*IAp_O7d1 z+l&`{R9BRW>rIu?PBm|1!yd2^g~f(Uu;keJh=E^ zN7i64(^vY9<{KH%2o|qQ)Dz^_(^TW;6uB!-iPO=t8EgYWz>~Rd%^)Qf=I=j# zlqF>?m9MEj=cH5cqLLA4=kWmAP(aF^D^vn_2s}NG`S$IfnwM7r5p@X`mzh)6M7_iU zp4kAf_I^OB4ym15ESWeLbH~h&{>Mva=VAO|xk-rJZaO6h%-nJJ5DXl)p>qW=4-L=@ z<{9d={6XI1Ahyywkgac3lK^Pq=A%WE$|Ni-+-Bmv@`wN^(2a@oAkadmim>Z}A@Cp} z4nL#rikW{u4anFNd;pC?db>Q)ClzW zVL?#Sd1yyDK*$!MsJX{Nh;2C@h!}&>;Q789p&j!wAAcLt}xFFmv=og@Naqs^?dYjAh>KYZ1umaFa>M&q^so z=4?3G7_j^frPJHF;(2>kCXaf*ncy(<9H098GS6n(a2{N!q%-9wz0BLHUsp3x$5cl2 zs**c9F*z-H^%$$tmMv;Ym5E&bs45|+x!-#9SzA%6z55#$X|j6b`@;$)=-BP_6~lgo zW>d~NtzbN(8!){9}3dWi5xprMt4VUQfYi!cqr{?Ue zF-~+tdd+2Mad<@lIz1<~N*NMvjRv&V#;%dOo(rCjZVo z2-pTBsCJ{?YeDn}%uRfKUFhw?+-Ptkvj?^eLdnSZrLsnu-AboX9ZkAson#@J8xvj0 zO7wTmO+f8t5D7?Kmc#&)`Jr8(3_cIi@pC|R)Q}CG0g8TlYkH~q4}GRW9jyife!V_x zf0;Z~S}^<0mJ=Xe#~2Ck3Fn8ijoH&Y+PFmsXrd1-Z_jAmnUSz7Q2ct1E?sI-6MZbR zZAQkx({b%+T~R#$;1hlO1?yZz2iwOM>`PGBt4sbb5(G?aJWX!t77hesvlgmmt*sDl zTkLu(%{5(h9pp|2H{{9HL)5k%UV6MRvTX zJ{DIaD!KtN(~y8DojtTzHi>h>42)Utc$LAo*J3z-H5gI9q*b1RR_w%kDPvc13EU+7ikix{Oy8 z%ruAirh<#KeWpj%mX}l*mDI!IY6Sfwuo7Xie^kJ6mCbVzYQA8iza>VBDT4N~Q&5zX@ zruVBlJ7%CRK`mr1b)sB;liqI;dMw_g!(o39w!3eizw;mhWska?&Fk_sDGi{@(Nb zfcN`I?i&X~)y+~mzBt=MfwJA$`P{;h8FsWjksL4ju)XaNp!*N*_ zjao3ZCnHJxX_<{ykt;0Ryf>kRA@|KON0M$jA6V@yr}z+HTKE21UQC zDSiLprKU=fF~s6J+j04ju<-faXqyZNt<=xhRuPQ&HdO4@PFKSIZ=PsfD?O(*!F39w zoJn)EG(a+5?4dx`9%az!ip>@*K4MXs6s0byUjyl_YxZ5>*!$xC%yS-G_SlnhDmN@G zfSS>nofXJAMYqJ+n9q56_SiAhS|k;2Ak66R671)B$r#-;q}u#RkZ&EK&9!c_f5q+@XBAJnc$d4G#R zqZ?PJ?)$8NqGl)SDUn6{wusiJWZsqxgjLMc7frejAe>x86{Db^H@jriPF;2R#4eZq z?mHfGtnm}Kl5*VR?zdi2$y3hYL6=}48X1Np3ei_EJJ=pzMIh;;1a|~$expj*7m71% zXS$T8bh6No`>=}!`=_gkM5sv!EcN-fALQ9-4b-f=g6Bc^lD#*S?_-Kn75{>?6x?q3ABZ#%8V6;!&s zp6D^Wynr(Lww)Y1T5+L0GPhJv{9_EkNwJ*OUpa<9W>TR!t0{zX;1qkof`?%CpI`*Ybouh<_KdAJ} z_XXHFAcN}{%he=@-I_0Fg2xb%q!DO!GM9?lGGVoef0%RAi#F+?Q0_nxYt-?nN=r?) zTNbJw{_v0p9^BhoYa@)+GG22jkKSZV1FDfEq8U{nY6* zSvd{R;@rTsn6~@ao{r;7Vh!x!#JUCu*=Cc*JM5Kgkn6CL>eo0a}O$rK+&mV zpRvTWX8Jl1o%ZKcY#K<9zqx2s^$i7n-c^q5uwz@~L$n|4YuuB-d z28((|r*c>WCF}=<0(HstT7HGP2>Q9s-o13QBwDps77+RvvqUWdnjezS94x3hJ9*S} z8TF_1{>`PgjX!@5m43Wsi-wlJ=~}Ok-S2~@`o%k8#zm8&YmOf@zD(MD*R%U&ThIRU z0v*WY?KfVwp6<zWz;!*W59QlImBFavAaxOvz&6ainnGI7 z$EJx8b#E$>_bT}cmlHzM6~LiMFDw0blg-{iVbNVGVbS*KF+B0Q+nOfb`V!OH9`}3L zLnXb|XPn$}^L#`)%J!v`5(Pa_dA26%EMB`gO=~^wU=_)ej4D}k8ykf7t_w;KyIcwh zEv=iScYeiPr&}&h{vUv#I{V-H)gCL+(6r>8aDFLI05x$rMm?`@c@9BjV)|Cs*A5S=fGMgqbtN=R7QOVYF8`F$xIsFA0Ap23nw^E^whlU zeS%D-ZOK>qo$M3T)LIM#98GQ+E(E`je5g$LHEruaTfG?$whOFJyS&WB4f$Guq?lcCER$z(rKmbCTk9J$(%T@yr!>ojslZ&t7eJ;h8k(n0$~NBpt?JMR zrxO{%;c#9LQot3=t22$St!i(!v_UnJb3C!Oe`4h%sBb!hd{z^~V$*mabe5HJ6a)80 zSY0FUp@dCF>vAnt7IY_e1jqP~QU4##M^pfS1#7Efk3_+D0kE+{eDdR;w{Qq3{=aCb zej`Z-_&SMfKuV-B?*$NN1oVMV)(dlQQfuS8qwq;LQ{VJJ0|4{D-*1(u4F+~`ddAKA zx`7v0qYZtQD!}m3OF<6n9n{R1r;i+sg_DQzFCcnf0Mva&w^SlX?+g*2nC!21h0Z@T zojGyD$SACz_?7GqE|Bxt?F(RFcL44ekdt0p$+&1f-=FFe19L#&y5eSA~6GpDRhV^Hi=2@qH4+c$G zmrjahY>+X&SF;fXA}a4QZRlUFE_7t*iUTTXob6hfNZSfxK++H$28dseC+8*K@ivf1u|V*>_Du^<%-6dGW!p zu1Jr1mcb5k7kO66tN*D5PwA`X(Yu0@tzJbccs=7I3a0>|pZ7%aB|BK(c*@f-0eN|% zfjDMn=1Z#}d9)y+pFpomaJEXhIUxF=S|^FscKGfKlQAikmurx2ee95CdN?7wVag*{ zhat3F{ij1~fS|7r)jy$UGTH>izR_ha-A~Aw8rSTO810#KC_rFr20QWH1mOek@}r6c zv`r87lK^ZhASu}o2)|9p*F;wWE{eSk!6)<{ut`7|K?!^G9Q{JWt#c8u?20jPV~m1a zBYVCC3=&K%?c9>cU7f4B1|MRopK3gF2`IEDINB+~LiyI57m@=pP%k;RBfdq4sn3-* z^gx3je@ zmPb_IByOHazF-Fw_&2kO*fN!TC@d7=Uzw_DYHlt7zGS6p*Xa_oI*X^pNu((JxqQ~h z%(>Za5j-hL={_5B&1`aj2tce^oP9DaIu8A8+gWsRydx?ZB6;3v>QLLGD4ngMg4oa6mG(DK zsVFU4zxn8@ymbfLV37|&Izd&;ip(?LE6~j#`D`UV)B}ES&KF+IYM=1gft9nX)mnJ? zSvt$ve~ZC@)%t;U!)|4OTbSyO&@=nY>PX#+^4*+jI3~6d`CjmVhCz~Jn$E-`nA37J z_8#eRp`&?ji}5((T)FVT7lfLpnmFkq6Iy1121%Vc<_pjDyx3Il!D3{ZaPOy_lo<}b zUXwRFFHTob8cy>66#jr^v!@!G`VA~SA5AivJ=*|h{j#tS zASB{Co&5)rJgFy|H>SFp7+(ab*XJE!#rp>7fMR1KLBEe5&o&<_TsVMm@^VLbt%R-B zSIPXQYoUgEmX|!^Csm7o#MFNHclG0GE*$aq@U3 zWx4Tmz=H42)NKP4%9ULz^Y)q47o)8`Y5I0><4T=e>2yye0_=I3@jxJun-2g8yKQ4* zBVoPF=uErcRXguu5s_x6mx#)S-Fu2$d=wZiPxW;&`0{X6Uz@Rbm#hf`*%$ z+YJDHKKapzPyTl=NyR91<5*IXV_|q5VKIgE+p_~W@V;Vd9W{W)PHxy3=;};bOYPBPs22{h))r}pP0V}TxT1fw7VaAX;n>w)7D(CVclzXRg$t#SZU9s|%{0-(YW zewk?l80VDH2vj%GIw~tFmK|9v6dD0eN0a`CMEiJu5>rkGOt~u{1A^Gv7MNRF9(C@l zpd&?v+AR^@m6iUzV2k1&1Cy$AD=#E(xm4gA9cp6sn!ehJ^1lbAs8%H7FYLfF(MORfMmR9{mbZ1 znvSA=0RhkAJr*a6aby9HPGV~W4t#3i^=iCQDDTM)qL0$%gK~csbQ^=0$rujn_fUjs zOx#db!k)Km$B!zLmAQX#Yd)daVq6X;QyAfeSbtFcRgO*SGbct#e!E<9uCRjAPv7_y zM9k&-*fMRR-O(mX9EhGNutaJ_6lRYKmSxG|0Zncd;KqRgxbxOWq~7nbJ#ODF$d3X_ z4x8e-pW!NS(D0^r&Cwz2jEg+OraVJmq-D9g&ZK9u@}ZFfH>yH8eE~fjM{UB_+e-eIEk{QM+NUt99byT4Nv~{{1x0M!>>N_D zfOijZz5qW>VZecrtb(-mQjshBxoR6{D>69MIV|CEW9U1=STFBka9-KckWkBZukCWw zy$Tgk=-d`z^KJHMCqIPHCVtn@0o|m!IzC`f{;FzX&>Ma@CU!)zl4=0Ts>+QZ@kWD* z{K)S1o=R`OPpdlJYk4)}*|#uMhUVi&^?4$Vh5v@UwO;Sy^MA@%bX{SV^{bP5q^8XIY^-RZlfnq)7D_~v;{raBa8 z6d)YFKY;e9zXZ(0s&P#{e8+0WCPRQKqOKGp0toGBn~uz01nSlvFzN!vBaDGtNH3FU zM!?O&L^DD1T0czIqxqC`z1-qEvb*@r8Zdv&h^n;KUH?C6*<{r!cp#e=ym<^=?JRmR?u8GPI zIfgGA+U+RzT6h7nB|mT+glA~H4fz&9yM&R%r3_AGyNM0?b6ji;j!X2@mDcNJNs$3-oOxLkcn@QfC~a&TW|0_T{7eCbYcR&utUbVBiOPdN9E0R{Dh5IRDjwjJ2! zSnH%naH;}^pXP8rSGn8(usT30`)QKo>n$9=YZMC_u~wWmRL$ z$vMClnb0^^Z|ON~L>(j6tGza%pXru2N2zUP9aZ$iupQB#U!Z|cOt#@gTn4yY*lmyE z^9J=))0PFju6QwS9|zRWxtRgF)@e^fP}LsF0Gv6x-V7cE!>;^@NQpVuwC{VvcjO*) zcXu}{yfBj4J-OX*UOPt|utJfnsq=>eE;WQrsR5xDz;sg5KXGn4{a8WKgyky9n8;4p znJhApf)3}!{)iv5lYIAEVA_|%;J|bY^qk7gGsP{IiOrG{ zVPm>iX=_V8JLKX^q3>mr9#s52Ud7f(@CA*1!gFP3fB0N?bEb2STV63^U*v6PS%PRZ z;fLAySVuGDG#hIr&K>L7rYcKx+1XRSojC&dCcywMfXm3IyUtma04-kIVsSC_$Vkjv z(RA$GLhkFl{eedtfVh-lLrGkBjQHy~3&^|Zh*rWRZZrFpO1fTErTIJnR6!sm(<2Ha zc%HPc*QGgyJ-)hZX-o6s=2a&5e2C4yj)Sl8RzrE4mAZb55FXH)F4bmiL1<~^{z z$$Mr7k2ZH_C4wgWd|pwl^}a3AGDmW^VaKJ_1J*txZ9XI2>OeD z>qc#bNfu~)iJKDJ-JhK5-S~~|w`Usd=stxQW-j_QJ!`}jwzE^%v2)bOk)~fqVC)C&xFi zJJH|Q!Qf_nfA9~WJ9p!*n3uCnE{JzXU*W=5LDV&^S=#%X^Tl~NN(t^zXA?tXM%o=I z#KQ%uo{>3NIVNAdKl6m*hv8Q?;|&!-1d!?lw20tl9wF;)DZr*?Dq{wyRa7;Rw8ycT z0P=K^eY#FK#pMuUb?rV?f2I41;D_E-pPSHQ^$k$t@>~K0@5;Bo5)zsWNYg+!Dbjy# zHZQnGh?0Vu*y+Fl>bw=S5K+1{DAGl;bpYnd5zcT+`P1}CepihzIwmG26?>>%QgcLClJil+I>!X?Dg|u$ut04z zlUD(7XkdWgo+d@m(3Xt6^FdUI}6C#<1W~&E0J>kc#V+Lni~Qj15pYH;4pV~ zQw^1yn;Y1Bytw%H&=S=0frM^9nl>feU9PRJKJ7nC%J$rolMks{)sAOip$XB*&Qi~>%G#vJld6-RYF@N|Lem`nKWdmMsD z0zvG6A1g3*O8_JST?W+V4jWeV^z?duetttB((ohQ8HU&399)VRVCrb}AMS$b!t2f? zu=s$eDhXRlKy7gb2z5H4g5ypyK&T^+LVX9BW=Sg4uLC-Z2Tdg;H5e;Lw!@RFp~=TK zO5iz!(6J9ulQr9rIzQI}!VqouMrN<$wKou24}kSdem4J)_TgWlyHo_M=Siad!c`LM z<^SL0|HYI1_QJvX5AbV(E59I2iU_PKVv zRsn)3Ob*}^1CF+Lq90BQhk!2(II&nDMW2b7VP<38Y5b4W3V&~*+p#TH*|NCnzQP+pU zKShx=vd-g4HF-r{DPcdMJN!_Ipq8T@9(6>$Mqk_@byBULo?v6sq5zErUkgui3)IYcL26O zz|@WchR|OZDfdX`d-Wm!Ykp(>DUGLG%wNaUG#&7591W-E&6A!L`W55|as-@Tkh*K9 z|6~jNubt}kBe@n$!0+4H1PyF2=}Z>8#IW^N5shRVbnQ=J;E9umQ&_m1f;OD$+F@R8 ztf3y$eR2+Vp4YGULV2sFC7wKMlNOFr(ocT^e-kF$5w9GT{7J#|o_+s|Ycmo{)hw>x zSR^0GIX=8_;qo)Oz^u5X@miaPRr=!J+Wo^WjAi$h{Cl~V_S|2#;MDGlFR+`@ zkYW86BHA8tk;+zeC&?@8m~2UHpuZjjq{a%eUV@W~vo&hEp`SJ-q@-%V7Y2bFJd|B7 z9s>*Mi=-6{`|MPE9&I({x zVto;$9uwW@EX4z*SiZ4YDFsTB|FA|aMC|*YF|i?aQpW?XX^!(K0CMs<(B8g;*S3Wfv%{?2q|TU%--%`Vsj%PSErNz>@i8V>hA%In+D zn`o{0We-_{^l&$vCcPL-rqKb7$$D!S`zWzBNS`Jly%L^RGk_pdJmTSh15O^XRrQ!Rrqij>w$HmF>iMarWPG=RMm}| zJMI`}U;)+l=dIP^dK?QKBtqXF)r|k$jy_}SKt@AX5{f=;IUj0%W^}b8DIj(-JXPoF zv_7fIzQmE@jE$YX|H|=X1hjKPPuwpQ>8mFH`=I_+fg8^NYka)=V1iUpntB!(qj{<$ z)!#?Z8ng|j@|TuhvbZ9eIHZ;)~G2@7A?g}tZjEO85U zBbgRG&JSomRftGw>IPMr29x=mQ4>}Kjl#&P+&^HT%%^(eRS&I=D8uGk;!h0J4a z<__1-V>E6E`oN>J88gkFVG`_W7^ght3cC!3uS?dYIGo2dR^^;)_h-{eoN||BHGCI+ zm=NdUPSXH=NOspxSGS>2MVBdq%+pl?T;wmfvf0a1YsOdo)IFSb+2@s;_juVkmWPU1 zgZJe!P~_{RT07kyPw`tynDSiy{EUFyfK#d^dL6OZYSp3 zHsw>>)xj-~JN^?AW#4V|JX=);8c>cG^~4}SrP>4c78R{A_*tL&(W0ri7%qcVeP*I< z?afBXo|;=3UpTTO)xS}_t|68*jArL?GH!MqejI z{V>G9@iQ))a+ST~y4l8F3$s0P?){Zg_b-dTdfM*HJhiWP{<1#TEbt*VEY7N_*Kd@} zx|H}(;C9Z335(~8R#$GO!5H(l0?U33$tozT)N@KZgxsuRogb~9DP0Y4A+nh`wOg^E zqOiAI8rO7nPK9qXw?DztER_cTBV(`w;gBTBb3-1+5{nXd(9#b@Q^1{RAR;14r}Q%R zD9p!8X`No34w=wUWr3n(@}p}~!!b{MO`d+OfB9xGwfjCKGzmHZC18lqF>mZiz2}jI zv7TI8?+uvpf7)G!Dfb0T+MlYGF#_y>Tj9eio~u>W5dn#ql}RRYq^nP47i{I-Rn&k} z$ETcoPF4Z}rmhphwXycdmeXzr#26N*F7)+yU!UN850{l1pNXOGn;j=D?Za$#iU(57 z6f^V~`pQK7)MJ)KON35%q#@R)LqJz`SzKB-PzHIp^=8nXPLGGPJEZXtl<~SJ<(BxW zG;V^sNDrJJMSS@gG1k&?yDXR>#q#|HZ6@Vgv~4~y(`yB#YOu#u--1vT%39SNQP^q| zoQtA-r18*gfbc4b_^q9Iv%;x#({uZ6kKc>$XRT#^&quS~Q{>a^Fu5xQnIr)UFlFUdrb=Vm914)^S+m`-9wrwYyaQW?#PPmj>t%q5jS?vBGGl z1Nh5Fkao{ss{72h~`r3U8>y| z+5#aim|tKnI=9_K!a%Gci$Xu^HbNCfIUL>DSu9qbueF|hI73Ob`{SdGs#|p8qSjLF zN|fySW5okd6~2pa)s^Yid$UHySfs2zq1CEza?0cvNaVL~KRgDK2 z>6|#?ZOH=gH@y=u;xe$~dlbn%tWazm!#mOK;tS@NVVJ{a7VUDx;{ZQ)h!9kk`2 z-kpI?6jA+hT_@l??kiAXF6a^^sKw5%B}o2$r8 zuF^akNHUD8!s-3#lV$^HX588jcpF{H8U|`rvftPE{hx3XCUBr+eXb@A^QVyXQ`gZs!dyLXdIbVhCXB}IU1C17uCfkB9 zb1>>Wa*U4Rj5H(eErs}iin`jl1zW`MmfgnB)RwO8yW~5wK`{)Xt16{$K1-)qbr}69dW>T&>AHmi zVX|ay2SbCLWjVEQva)wFS|Gsd7kgQ@H~GwZ%?(})NIu& zR&NP^r9{<*h6}>}edW(KzKE*IVzyxpU=(bsV~bJr z$W7*bb`i65V>4lkzF(rAGnMS?%9}dZU;=-w{n$C|!{|k+M~n%AzEeS+%l|&v{PmMo zK~p?vxGyUcj(j+@dnKgBj?X*)PLAvEAp|fdnN40AmSLw&8eU_UbPtvB30F;I+(*bRV0bWMiO?d849}@Yi|BQ~ z#!Ot75KXDTTR(D9mX?`e8>kYn4akH0^!Bs31pd%IwJs*7!4xE<9-pdM**UmXeI>o-NBe%+)0H3|Cz@l#yl^?!B=#DK?m| zDFxkrW<(3B!D)71E0YU{g!lFu_NT$NQE*x)584p2V5*(`Gb&k)kyta4_azzcLY*lZ zqssWk-*Pu$aCzdEtuHSu5haG{WDY0`__(vLA$|CgOAd!HgXvckR1toYW74#+|h>55CT&duf=W|5SVrWag zHfY~4s{2rE#%#BjH0LuBjJV_5Oe0x?++CFeeOETO1%f2Eu=P&7(e8)G%+hv)0?G;ZXUShJBj2X=1|4MPy7+4b1Kox+QW+t2mwcW+BZ;dcra-6o5G7yOD?TUjQ$FZ@`UZ^Xt= zZaEuFb;{V+=#^&)=n+rf-}&SVnYKI32G6P*s6oRmboF$`hB#S7c3*zdeb)^MZ_~A% zM#D29gPpNV;!bWmmtHP-bC_6nfJ|8%os$Dw>6^OTj zTXZh^dw3nXa;vChGS)-l+-ey9iJMF#FSIXB`EKj|M80iEJZ+7WqA!tT(6RRUhGxeM zDaPFD&})UjMnoYaj%OPzO)tN6!`7HB3J z{CWU#?a-@^Lgg4l+XNs5E$2~-R{cJrFiSZ#fX+OHrnl?mY@E9Q`4|OIrizZ z>~+5$F8X5@FUD`wk!vVF-rUigtn-33%I_JBA6-gkV#*Hogz1XNi!(HwTTk8`d%3Ia zkCU;f(|#Zj2zWE@oxMblszt+>XLL{SUOB6%tsQfztBmLr1Wuz~H~q)gA7O=BZvrUJ z@a33KG2AIRPZ6l1(nR~BW~!dRcE1oC{*3LPfqR0C?TqfNH}t51boLT%>gvXRr@bfY z`TBQ@{)x5%06yhOJky=EFLaBhFR4q^eO}6Vgp%k2{L%vgCnbf|mxRv`k5)jc&i~Qp z*u^~7rm0y#&7{I{EnhCRv{V>B(ldDMd<qo)$*3bCAq#*{ z1135+a)wjl0={V5NK32AOG`slU7f6K>@AUyo_=(QZB>+Vc|0hQL~|PZU=Y2~C?#~0 z+C-9oFM>?0VQ+>{_1dLf%*M&au_C_{>Rk5RwmPhfiVytB1}YB!HYneBUvn1sxpe)q zj;;Jsx%eI7(3TP%5C1`iG5oZJ%4Mxv8H4UqrcREtr!1S#mn<#jX#HK+p%tw~uamU? zNsGhVH(q-iYX_D0FX|cNybQO}PO~d;2q|uR*4;H0+GzHJu z8q)Ie;J=2stEHu*+bgHn34O+Vz|^dbCj2#ASxMO3$$`Vv!pY2%!_&bTaSM{Dr!Xiw zSiUxedOFxUx(R!V(f)IVFeoDqbJ9Zpx#YE-7%f~`6)NrIY6%tK;Nsw-6@LJQLPcFI ztc2BNWdD6T_$EgC>h)`9VNOmD4-XCxUJfT$Yff$gIorf_@f%AjTIFC(exiTv9TBjri|Wchfc<*?8;^bvi8j9L)pgQ#pduD#xU-rK8FQZ8%m z7msDQ=_JwABw^Eq0jQC2^tD|zf#cU-ZGO2NET{E)_ZAs3 za?zbB(PH4`)lg7S2$4dKKqA2OKs77XDrF4Eqh>4)y_Qq=r-h*^p_;k5x#brX4JoZX zB!EI>pi2a*eMAQt*>^GlnE1%7kZMay%USaSLS%4t4^`lN)giJ73}LTII-t)A0~0S2 znD$4NvtO48+eZc_-eAVg?<1|#fa3ZGq)7Ig!B{b1xa%SEqclpWC_Sj-h5%X}dQVb= z0qU5iftl5!goJ})hy;Yb;fPxL98t3fF!?6nC>9ex@aRQAAJLup5EVW$tw}giDMP$OU$bTBjQ zdu|K7|E=*sQugxl@{@Ho8atEu(R{BaU}omQlVcKLet)*cH%lu!;=KNB$;UG)r|%xG z1>=q2N!&n_4O}CqL`5Zm;WdH?Jd4DcQXL(^<5(JLdLf~OGO~L#Z2F_Cy-G!G?W9zy z_ag4Q(;o{ny%p}R_BbP>qcyJQ4BTQUo;gD)TBS20VKTJ%TpA_1wN_D0vhPS}xVgDc zt`9p$iH&_|6q7j;dwQgV{w}#~w*Os;Z*kwdGl#vW64NVCN~I+>a1K-vwy>}mno^M{ zzcXC_-MGrpxcnw}-r_e$z3R1A$_sTi@k?IY88!a* zej55Wg{A;&DhdZ=|W}@V&ty{;>1j==@;WuA#~LD^+5H@AVtr7!fcZZfY@6 z`^?BiJ*#&6Ur_tfdwF?1e2a?h2xfNFG*@vI*pSkEv`RET{`n%J0(ym!-*!6Iu=UOG zKnyvW`{H1w$z^wW$R?lf)$7fzAU1+&3D2YKo6{*39`gYlW)2Q*hb5OmO7&(}OPht7 zd|~mkX^qXCa4K~f?4Hmi*HQK$lJzvF4rb?40;Z5qkNexdqtyO4IVWo>AE?AEE{@i1 zCy}(7*fi?|{qKBuKQM_LA0L;*1fpOlw0oaD*3Ipl5aH#mHF$!Bg~j9gQ%>l2-IV5C zdOF2Ii+ich#d>&+X>XY8Whx9cmlU}(5?7D`6A5BBkl1#6{=LS2foGx7DN9+P$&<75 zrl#h~8A6CtnC=>NmEn87a56;qVJMD4ag3+lZZ4_A_nKv{5%lC_p$hfpqS^VAt8|}} z;SnAg3LFRzM3?p)Lz1fRs*D~-YUUQUtT#D1d0JuMxgI(aBMaFGV_;ygJ@=T%mQ1*BDNNn<#)Bif_;UZvCbI$>!5j?>I{EklU1uE@5sj044udU=8w;Rg3|hT z)!*29I|)-p4C2?CTW>qVBU#79_NOm0gWqm;h$+j*OjC)dMf9k;)IezCb=ou$P=wZx_LY#ux5WA<1O8n)(jgh%`F0J#Bt%n`@_1MIZ2_Y#Il4NqI9Qz)tzJ*q@>rp-J9XkbS3H_e4y4Lcg z+T{if$h5Lbk09819D=CxV)r+HN~o)z*1!GWR%Jw!q80Y?6|t}HaX+z^7rX>aYAvse zaIh>AfSF>~40Rs&`S_4hxX8>opJF_r1H1scK9xr8_Ok;t1l>MxG41XX?7lW=2OE3$ zqbG00xc0DIm#fXg^_=Y-@UG=B76MI77MdR|ude!CD<{@&p{dz&M@b_%zt8!}g z%eE$SN$#6^fI>qO3Nh621@tS^uI&Zc}jqXL^IG-W0+t#slD+TjObon-MKf5wCp=7>)u%W;WI5ZDZsfk2!Q~ZA&I)TeJO3&;L?c z2}{}!eb60>N+M#{D`H&UiBOvOmiS8N2_e~q5TVkWSAkDFehv{dU+tEn^4hmd zUS%(_w`Y#4y?sAvqD#|I(5Y>fg~9O*auMskH@lG4AT*F`hPh$+Wg|}bOPG(dnDzLl z{Mq6K^XSxXq1eQ%eoI4D>TB9I zUS~5pGvpBXtEjYP7HE&lkAY%cg|eZ)I+~R`IMw z*4|vKx3lbzdcsjUu+Ly%tHiV9y;mNWlJY9vzb6FC_Lj;jkOnHH6x2oAH%am<8?AzS zMJAx>0rIaKFgssKb~))hhM6uP&&Dffit6@H9FaX0|XHYQZzNuVPHmMC>PTm8xWMAw7Mg`_6k41+}R!?Sd=Y zPNE(x(!G4E4 zcidkR;%ou=l%5&bqq-(DG!jF&IRQd$Tkx4pae@e2REU&43|~;d>NoycSbbpA2cwZd zPE(WmdkkuyHRm8$=V3%>1yr&<3_A&zC_mR8idtI=7k0V3+)iuhW0lj%<|Eq}ZFREqq4UWkbfYI*_EEJo;_}B?+EVmRy#N+ZNvP_OD~T94T*| z_c6yxXurq7kzZ+{!)Nga1QD-M&E9Qdy(hN1TyTRwPGRWBwP}}M){54;bDlcdLQc|w zmy38*ii4@%e>I;oF@nPESCh}v&VSituIk&pMYZCd$#N6|F<6v zynwm0HG9(yzgpIhdsoh>9@{KkBf$32$6S#Hk`n~e)lHSwGBY!yDB$Jh!^b44zLd5N zfP_Q{pCpfC;#w3v*Q7&|VjV7`^I9H|^GownHz*D5q4T@9k~iuEP(PK~MfYteD)Fae z`K}0MeOnPLiCqm9a@@`-YrC(7=qT`~;^Ib8(a~BlzZdJ{XUp|Rt+RZIx`-h9iCU|$ zS&A}ml;Y1!?t#-M!XIKk(^1FPz`yI~HN7Ms=Jsp$#0zJHgQDm$)9 z3KzzG0B%SScHd1I+|KaGYy&&nR&}LFB8yg>)%d5>{xm@+jz8O9iR*~JxTa5kNi6$A z&GEu^)-UiVmZ|bj^TwEA{noZ$Kds62vjPRvm;-&>eF>qhl;T_@2^% zC1A!4Ag+-jwapjjF ztkZVRMS{_B<0S4bq&uz;M8Yp&Jz{Uq6S&O>CYx?R^pi4}ef#Eknax46e^ycBvK7Z<15 zj)0ug)0#66sUEMhc`eH}*G=zVqmSZRKFtY*O|T1Gr|1_APMXvMheuw(e%|E$W6ZVV zLF#0!Qf+3}6h50cC9p_$+@9+vZikUOs&4@R`sNCD z5=Aw&kMF1@jBKZiXhK6nYhLfD8|Ez$<^3II${IRYZru#Tpo%3n@{~+`Asf5+=c@!K zG}&@Ai5Y?>L|1MzRS@^_W6n?=FglX^3wUZvU>9{(*j1w9c%DZBw&`jk7Zi4*E6-BsS~n=Efi) zVceV#0-u-Kd!~0V$ry6Mm+vV>wyMPK%&e>mZVvtLPB+wOqgVM);yU*ed;u+f*zYK>P5 znD8Kr*b=P0xD}BQeYid5JAdH4*vb-UPXbSCOgol(_G4)i8Bgz{peGR*$w>g4WBFxb zZn!{o`|j7Sr10rbMo{v>t0{PIXnuk;tTlJ@r6x>jB6587%;8CZWHTcd z)Oj7>EeI{-8PU=R0B*%?!1>T|+yu`%l335!B`iJmu!1dOLVsuOE1bl(>_e?FA%;|c zACke#I+v4SrqOU}zgOC;tsYMN3rLVROjTo!t-l2}M|$*T@**N50EdxQTMS?1cG>8Q z&|R!rU%qB|ek8&Hyft^rzpZ*F=&Sn=1=9YsZgp_<61A6@z5gh6BS4AMiy|u&BZ+bu zzy`BOc?#pA%yVDsLau-AOi|4PH%X!F!+oC;KNID~cV2a8;@<7>d>ON96=1wgD+9{J z=VS&il~l(*@sDnWlMqMX3Vv(z`jZFVIu^z-zib>v5(S0#ty)mQGWOujUveffK}!=% zzr$#$%e0%ayeXIX=L|ka1^=XRuI46;@)LT5Pg5q2x#8qZO~~8D{TR)&;N~i_FX6Fw z8P!P%kF2i3FQ}?%U)X}`9_MQ){$|j4hKLUS1O}FA*vWg5167s{dB4NQ%K3J%4H zqvSGyHQRDums;za-kOS0qQ*aguBsBolKCAAVKCeVe;3on^HP&d{@VoQMSl`V@}v0O zQ1NOm@rJP8%6gHj_2vE)W#Vn&4lZLj|8@O4(!nd-rGFM&0 z4A$fY4rC{jt`fHb!K=_n+$I<8Z-T;;<<}Ga;+6GF63vyt_Y;TkYsombZyM@E$-h(? z;I@e?_l+(-=Cc=F3XWJ_;q-pP`s(MsXJ@htt)(1%(>Q%iQjcE1;>jDd?KJ+5DOd?tiVr%xg5p;VqxjHIQky;KKXb_8mYLqj zg2Cd68=RHPCH9*Ar34#xr~(6&shXUkgaAzjg**v{b+I)>sU?>Kx)FxD1Xx424+L)D z3%(VCaJ26#;sBQ&01mTxFamK8CNJPBO$AIelYxW&7hJPs-baDpGf4rT$wkgpq6mn{ zx8RxsZnK97f)WRy-V|Jt83sU$iUET1F9|mWFlibF=+ZItce{YxT%!VX>DzZH!GQnd z0!%BB6^g7ofYQ!@QXLnQ9YgSrDS$e~u0{Ub0_ti49O)Jn-4+zkt7Cu*4P6LOGyJFd z_*M5_YaPXFKf5Hb2Jr6jQ&zL^9uAZxV%`v9K7R5<-o>SQ%K!G4LSa?IJnR)9tD@%R zY%9W2w*g~H6Yy4qnOS~$b;XwYyboP51~ADm3)%qx+#jQ2RI`0sis5u_7YnSk_|2FX zu36#y)!%O4Ek&n_(LuikT@_GHfkuBUkW->qT(oV~$t&~W1IM;w z+2+$p#eAc7Z*fBuW@hHNq@*pEg`Ynygs%5%XXfNy)5O|kiThdrE?rpO;o}NeGf5@< z+LT*LQGBqF&k73)T1E!kWS}hg5iDjX&8LT}#^=AkGa-CN#LoFe^pDb16eK6045nAL zb<}IEVS)>|O8MVEN&>W|%k>4Hix&~*0VlUe4ox3(QcbqAE4sz`%Zh@`Td>zB%Epit zyIus>0Vs|w2ale`-L9%cYtgJQ(A_~7dwnb1c@X{OD?331I%@8S6Y{sb!opF&fc~7X zHm|W7Cv(TC85$jprxNp)1C-ojp)$i(?FOgS3QU}z=#yPe#~b}o*=yQCnx6ogzJMA5 zHtKO?xk0nKp&(%FY9132mE1aYTn=p3*O~UB&zu|k-!cG5tX}w%GG7`{WGvxtvh~aL z>ZgxE^U!_=)R`;qDNA(2gP^8o|n{$Y&Z5 zo5W#QB8S|ET57Z26I$bU>rKjK5>WHbcCLaQ#3qseT9bEltfss^$DlKkcfUf=U47t4Q-b$Q>z3<1MG0M1l)5xFX-%TDE z@c*vZd9&8gO~%5?8V@+1R7VCjT1+`~A}Y*s$K{qNXINC#T2*Mk1k5M){`Sy6$`hvQ zETQ)IJ!Qo;nycMxS!xM6Dn@CIWH7pdPQpEarB9H1d4j2+!tiY9JYaw|nCcG8IDesp zV#=UEkm&^c<#1ua_-ZfiYDgt}DQER$Wy7Lg*qH6kRc74)4s%3Uda*kgL+iTL<4}85 z|F1SF{({hUIvwzZv_s~rOr;yO0^p{XXZs5p9~X9KN|j3=g-Z4bR}}osVU3v2%q!n6 z_x*;tuT5$Ng%AY5{@Vd0WQz$~-KnkLwh69!V^UvdGbO8CuE$VSv^LVcf&8&Mv`?I^ z5`p{s=hJ5lRaK#YUw`UB#?8~W56c}2_FVW#_hMwTRt4s##8>y~o0Qep%yKsApU7U_ zW?;MQtXWhQG_!?%xjNe`)z#|ziZ2op8hP{ns>WuD@tV;~36+*8Fn}Pf5wlx(^wo@h zjR1!lM^WRZ?=YUabovAOZ_RGjLf)rE!sw*kW)Pcn_gP(*UmxH^lcr~{>@Y&YJ;_lC zu_G*T$@t`&Qx{!({AVtAODhx3LVcjnX$^IC+PCNXYS(gekj6)Z#862kTErHvYxitm zpIwHK__g%PSBZNA0Fqgo%6r3bE?OXoIi(J)!V_+Q@5Ck$ zOTJ-?Z#+Ub@Uzrp3!mP^zt=7q&*JCHSmKZ{?|rnL3orE#zPi?fUtS02hrhYmiLMn$ zeTgcCjO?ktG9+PDEh(>TyYiHxsI97uB2rN1Ckvt7!tM z$(SK@5M(9+2d_{%lIc{i_7B0JYkfqdRaL}bseC5ARyXxB9xPcxBIT3S;;$EKEIIz5 z{;}wa98V)jt2eJZEqmphg&D#D`Batu@wrH`lYj5-`cTLH2#+Ab3bk&NNP$Eqzdx+M**e`N ztnFeWdJ;I8XO_5o*=u{Gp2G&%9ZSfNkTqo#A2d|XUOHueCtUY8JCn}`-cy!#bmS0E z?Fs{0UM713AM;Z`(FnIdQ`UtxnUOCU2nFE)2g2%0x=rqT&bbYWj&x+?kGI#W_emf^ z$J`TKAC|m)%r2m8@yZTJU9m~vHy7PoJAZplJPepV#`%r-AD?AoPU`fw=)fKe ztc;ss&ybKWjZ_%6j_g%-R+#i*&;zo=+h(<+{pwjB=RELRB>isBbk2f=M}Am(waI)S z{%t*mxniB-^DWUb0>%f$=QG3BI>M(5R!V>NO_RWQGGHd`gV+`A1s{QUu*DM}2b2ny zP%sOkUO-Ly!Z_D9((E$_W+~jCxTEUhYxEXIABF0%U!>!U-v$q(`Up^Dp`UDZimmT8 zBSLw2;ukQT;xhibEM&rm2l#;xoHosm&&{(DAGe*Li5q&$iN>M$(1c##YIl z+HyLQq8C&#JBp&`o*zlRxXD7LWSv?y=oMT%qT~~E;j)`$JCk>T`(6xkrm*)9vPot? z1`vY<;3r-C3uCnIn=%h*HuCYB@qwk4)uLwj*E^r<-;E=b{T2{REN;QEgpDN-IyH%> zzRyFu?l!iPenY5-B3&8$t#r`m)Z!E#((rvA_@fB^glh>hvA|TLsiy zbYNf_hhGE~F+lPE&C$RMz`V&QTQ8`A>T*#6FTkoO`$AiS=h%pyS7n12DRPsXS9YrxCvazy$yu_jP=m3|wu7DdF{GVbtHoFEJXKImZMngU zbnoTB{_rCYi{!_Ado<1M9MzfX*E_2nP^{M3#Y|52#J&txTrnbY2gj#61L8SN+HOL^ z7uxdIE}!TfjI7+Q;De^F`#)>*7wh~}cdQmqJe|;_GEtL@@a%zBbGJt`yJCTdPSEM_Q~T}Cr&Mi4h^OS`w#CtVG2`ri+TOj zON@z~K4XmIiB#bOoovjVUlS9w+{Sp`PmaY8yx02MBrU>Vv-dTwZvAZ}GqXT#Gr0=y zk4Vb28(Fc)R&8?gdd--2{>s)p0zAXyPIlCI}18y{-l9I{k64#$M;U9GLHBAf@ zS?RrcX_4PNzQ-@(ie@kTx*4}xQYq=U-8B1*QG1iMVsTO@&Z?>`c0q*sn4YFCthSs##X zx$FO#)kMO+{o^m`S=|8>M#b$kbeY?#^~)Pq71I3OFFQgx21y&cF*8Duk=$QXZLCIC zMYux;m6jDWSQP!Djka9cT;lcnpPWB;*OQlwuPCuEJMJ15a|ohn^C^+DPuE2|c`P6@ zBigWvO#0Y*n*Te7l?z7WqBWt8w~1&}sbDqpkEi3Gx=Tc;$ig|izd)m2$h@wnD9UC)*{h^Rv&_ADQDweyxU5%AT;N8W~)!o#M`?t_7Z# z|FCrH84mpNA$)bE)i0rqV_V*`mps7JO-#>4$m-qgI(BAkPgn-M&FBZK(c!($`00CJ z`ovB@^@lNU)oyo1XXk$GEtW-yFhcw#7RU0s4u86n+Qjy0o3CZ^7!(x@biFw(`AGW% z3;mBlJlTU@v8T!}IsYd7EiofbRn*|g<11f0&ieLVgS#x4Cm3$##*<;y9T}v3o5xol zrO9n@4DWp+G`kS9xmzRfRmD5VH`SCOXkQV>;{<-)9d@P%Jq~lR~6}-0J|KnIxt%^-t-q84x30G~Ne*aozdTs9vPLy0^>+z<%?=i8$ z$fwH(gBzd5f46p-bK+yR^cS!v)@}0Q=vbE1J~j9mDWc<5X7zF1ik{4cu9Tkam;=)Q zKh`RHb4`nPjD-NN^zKXI7WbQ)YeV+x(B?4mxp8P&NnC?Z%edDy%Bhge_evJ=h$seW2?6D)Kt2?J}4-CJ8nuMr;I5) zl4LGk8Di9TnYr41cK%B|?G#2i?dg^ah2nO{PaBQIu__juZa$u~r6H9yj%&Cx%jy>m zV+i(CTjZo>!P&q&OlYh4oS+yq9BvX>I8Rq)xOcf8mT1{D8WtMIzwb!*u(>-$2EJcy zJM8bnO6ta#c4_X0mwKJKYf5xvUf3k7VZLGR(M+}!Q|KG`sq!87fPZ+@HWiu3^Y;(i zskKU@@9)xQBCqz`<*m!=BHHR=lt1MLtt=%dZP$WfYfX4QDAm!ZbWf$SFua2 z#Y-RMlkb@>OMQY3_)YOuelUCx)0%Ii+a>XT^e(ly$NRU|gfkV=FFO4sMj zRf_2`O5QE4@FUbgN&chm5~0{chaWr#diC&IQxS@wze340uUSm_i0>-z^a;VL*#oep!!EovDQs=@PJ##|@B3VDLz^Wz4 zmMG5Bpl!3Gsi8P_vsN&0D(+}7mO#Y6Yr!_-seRHCqn%_>L(n%HXSHPC1m%fgK;DTq zikG6}G-$5NvMTb#Kk28QXTdDmKBp*bfKz?^ZeHenV)|xOpIIQY%2GaY%*@mKhg4%S zn7+JLHR1_v2gD_o6zAJ6@5srgE@rCie}qs_y0-p&Beq&~PNjC+kBgcsZp)@yeA=t8 zZtyv>YqFZd=9{J1f_G4R1xAIL+HL$a8+Ve$+B3`XHT~VuC(|$cemNA9T57}8> zS}Q&EEO{}x#V{c46rgt!G$Hl!aC92i5w~WqrCh(9nDL?WZzz$SgiF9p=KI15^2_51 z_L9}^rj-`C0R!`fvk*^LvE4TB=n3qliGAd`5(ai{C$tcwiJHb1CNNc)cm8DYRN5J4 z>L21`vQ%%H+JqArsOUPpjn6B~v|j3Jl#8sEL9$3V-uxUFQ|xHYB5L~8d-!4_@JG@} zWJllODy^2a0p|jWllD(^I}Z0vw&9CitlGvZ{1W9f3pO1R&hSA|227$K#X7&+_w36s zY6H?Tq>07t;!$RGM;0#yY@WfExV)AH(3wBb(g7Il->6z$8}t|+tmaP8w~I8 ze}mWXOf*ecE4E2OX5NW!xl^)ZRC=q71f+5Quya4OXK{90V?HlJ=k7t78`I*;9$T`>i-2^F^{etoiJ6)!U8T9l11 z+FWA%b7xnzY7FqeR+g}&F<9(wPinkmZSW#XH`-+t1Jw)=8#-Ik&DoE;$I{Q_w~7eL zXr#(|{JKl{IWsrR#$f%Aw{+ust&Gdvcp?^CCiv_V@B(tH5)!bf0$PF$Lz(8xtK3a) zHwiGA%~&g`H2&dwSLpChhfxl-%&-8!jR^1u*#D;YLq34)n34e3(mZ>r843J@dw~5f z!>JX42o^sGTqEVITJ#8D`8z)>V!tA3Gel3THN4vQA-?#FwmY6Xk&cFcGotBZouB)r7UarR< z`2la&gGu`H;B*kEEaC->n>3<)`l50R+6V}!;Zf|bczm)qud0JVJx$M{?Ci>BI6S#t&FWqr(Y7cd9KKx+v?IF~jB$Eye>~C(}iY zda)JASyYV!zR|=0rzkHxd<68w8a8l>j8)A^!Ba_*0Y^p-4=)k8+~5W|`;P1Z9?;?t zKEN5rQc*6=o8%IhcPg%qjt8s86Ur(o z$zTq%@=S#gMP+2a1eWoUbua^#5?DWhF!f#I)4Rc^<-9tv$B0Jeyhp;R^@Q7Agb%;{ zdw}ciTTBg(%5>48nyvfwsDJ*a4qCK8c#6Yo2|CMX3wWT;fB;$nDG(U|iW_>=CYWHH zY`iacN*I`?KSIIC!TLZcx{1az95MT=kI8^V=mzMf=S#6Q(x5@H^PafCGQJBa(K^a4 z&;X-y_HF10i_bubKrn9~AGCvBzz79gE<*u^A%a}}O$2z9N92`==cWZZZ~Z=8W)T_C zj>wof1ock<<~{pHSOO(2L?)nbzz#flU<4wl;UYcq5Hb19e(?XV%~QSC;hW&1XJC*A zQR(TPr_+*$H{Y*cPB*Q9=*kNyK7KTA#da^AM46n!RDm*N zBLKSu?cV0D8-qFl#H2@nR-1Ls=&eI&YXMLON15aho--pljRS(5D%$ibH#!ffR!3-C z4_fx@ZK#I&7^$RGpV05iJSnGxO)i1i?(NVW~ zX1Ft8?bY*U^A(A8JA9gzJCfM-N4f%0@3}TlcUaNR_@dGc-r%GXJYHD*_a5X{V{$^^6V(9=pl48CX zP}>yT={Y3??!AXw00qiVz!3`WRce=idPgauz1Zl)iO_4KU_2ZG46)_FJ5tx9HOaI$ zo?s>fn4{h>3XJ(}Y=!s$-L>uB{@3qLo$Mg6z$^b^o1EJ$WXZV~H_8VrPERD=50-R~ zL_pAH6HxuhbT8ij+K=*?urf8x!6D%SN`CVvYJqKw4GyJ~cZc_PDzg=atOzl_xbKym zlF~cf4(qR(>e||jo%eUGsbLv_tv7aT*;uApC87? z@+)U4DJdlYH9$!((EJ)OrFqjyW(|r*;0HBi`rmoUD=TXaP9R@-0RArwkD8-##ks5#xX#?(4lv`U8d7izVh05Y3Z zP~>~54(IcV)YQ}w2cv4UP}<(wFT#q5Sik$hc9wrqhKR>v>G~PB9BvZrRn!$ifTV@7 zZ7Otll+8)gdw0-H`9}LW}+Mh;RK7_>EaQH3~4MQn?zG;3>kmi-R-pqi#7*t z5>SL)8(UtspUvu@fT3m1?O$wdcH4e{iL1Td7fCppc*d{}`c@NF6o~6LgRHErSx0eZ zIe%^@8w(6SA?A|$ScI8?$JSVC4ajIP;Tsl?g!Mt!{{BRc?Hb1rI&Awh#;?oLeS%>TtWyF_ zIi806#mg<{%XrV8MPATceiaUC5Il{p+QY6Ii73e3&09IsSEvuEJlB{s_=zQ_ zcfq{)^2~7xxB0Y`3B3Fq(lf^)k>sxm_BiJl7%bv9;PR8o^&20|M$x?kfR!lpYKYl7!T&`J3LsdkfI8 zeYnjZ1g2R(>8nC$$4+}&%g(EX+8|+#ELE9GAh)9XO7IkfcgIT_UwSn5KO#&Bys^hHx~{5$D(=P6rzx;umTY(#`<^A1{Au3K)UfL_Au#o&h(M1gfo}xPHNXo1 z)w2WbdZ`XV(L`&1S`XqbTQ#`kmdg}fEiz(N-p7fL*_QfQU%eu)Obx6fX4kXm@ZKyC z&SZqB#lX_J)4YtK_|HIC?^Av6Bxc)>2y!Yxp)!8_J|LYbl*6sf&<>2Qw;UyU4#m9c z<&iAIJ|Nb<`Qgq8cfG)Q#KwwQCejq<{9V=faHai;*9I$X)d(!%;x4Vd%QqwxME-F4 zs*b{tQO``cIjlT#L@>!}^PL?`O?x&%9_mdnv1Rr{%zp);br%s=+DJP6!t55w^mdin zi*bLms6KOMioFqX*M)Sp{)9D_;kC5E2~f{HZgZ9W*VIP7rb_eWZCcZN3y- zM6_b^l`1hnWRM`(gu%CfSKE1y+NL7msSig)h6gObiQLD5*~(Nh%$fTF;3k>?B*Sm` zB&a1||Am+kO2DoKkJ60>(xWcx&wC7 za_`YQOI~^jx9~|0pvso?j;nTOK$E>I!&Mw}tvESBKy;Zd4OEC#^^CAXS-=ETo}a9l zfj{vO6}dzTvC+dqX}BDho{{p~YKGS9e8cU}FDzW$R{31jLNEKcIG=wX`s~Yc^P-ZQ zj)#wJn1zGE1_wFVmT2)|s7ES;hb?;?x{Zz*0GDwF@Q2H4frYhY{r!^j-NZ<9oXt%f zuH8cmt3*|r1|U(Dmy?T)i+fb+eSdLBYMoAlKsTLtSEI7Y7Z;w*KjRcNy@85!Tu22^ z^@WX1@yJ0LrOKXJ$B`sdL`2^YC@G6Ii{k*c{+_=vyJrRx+Yp-7;qh^I-GQ|Drfm?F zWd*Tx0^>^a>vPCf6s>jvmO^X3utOHZU09uJJV0-w!Fb|NYpuT~pzu=|6E zPe9;0S!1avucUO^+yT;HhQOAgS0O65GpWd#2-KK~BHK1SKxod`pO6Ro#QrYPA;jT8!U)H~n`DdP6P@t=7(hTyHYXG2 z%i6sDOp}3*PCC=)ME>k?(3u+|a_iK2%ZQNp)~wvjke_t|v@w}2iQ-@SK;aURsg=ZO zOqI~0Yx9d%HDBS$&iBSjKBpA}(6Y_yT#$l^Tw5~n3b-E@-*TUyjPZUQ0>NY`a36Tx zk&h9Hja$`%BFGE^eD?K2e9bnYr}o9-4?};0odxwLiV0ASOevqrJxs6;W%;G>PM&N3(X(J&i!`gge>1#ry>mB#vp^5zIF_OHGeFnWEh7VT=V*g^`;~Z} z98H4G?C&Xr)LUNf(qsbmPX(8pgWr*GOC4$2FV+>8pjf)7!C)|iqX1H1bz5E+d)q3f z@;q;F_+Ak4M5RJS{8FaiWk$(K>vb^n2)KRuVDLuj1N3o|$sjy_0LfSa<0%%Yy2yZffAr*89YGh$lR1(NJ3P^&fSM$(ii4lu zUQlc4I;)5RH)A&^;(EK`yJP&=m{K!H0G#o&*8Lqu`@t;p+ydrU?&%JOb|J2)1d5O=!z!{ zLDvOHzXevHlEu;A-_J5WL;Wi-`myUc|6EZaWdtrUq1?lkj{CdoT~UrEeaGhK%~=(Y zdg#q4N2?M6Nfpe{*ylL3hGly!_497cxg^7p3LH8CROM$#1N`Cg0XH{3n@Jo7O%vv~ z1j%9H!KwowF;Z*kH;`Lv;d$R8H5yR^x1i+UgTx&ixSc5Vhr49Ka|v;Av_ScY z5@^+GEQTK;5{-Fw)~R}~q@Yh!1U+)U%ZkGTplf+~^zI+?3kpWSTkfQd+|#;SQkvlR zk1yxg87yut4+A)U8gKjf^`J}yksbAGvc{YbNK|#el_VT{C*`c%74#=;el4SGvbR18 zgO~BK$uZ5l+{p27XVu>I)sOc9>l?Gvw&KY8Zay3SH17LK``dhw{S09pVgB zdDH7!GcXIL+KaQgKeK-u8*=_0qxx<{GFgC>4FtJ>aFz<|Sj+x+)r59%tpZ_l`39*c znf@HR?6olx5~UL2w*Z*C4ZF~Ou~dY;nXqmmpA|m@O1GoWA)JsV0rZCyPn!-1;sVz6 z22pV4-ks^W%qWV!2Hu31*hSS$ z?|xv!Q)6?}Ji}{r1@s0-sce?yqO+w~WP4NDJ0@xLK)_xUhu6J#vFSg`mhSQ^33oA6 z^64%O@Cbl&qbj~|&hljGZX3AE95~V>r#>B zgr4jWOb;${E!}{0dkqI@K}ZTII@L}DCn90T*0n3+5!YW+e$zLDsV3T(jO|8^!Ouu# z1{u;5r*<&AXo--e`s{eTFVTnWfIN^zJFJjy2^F6)v+5vo%vtg1b@=xvD2J7|Q~R0r zCd=NNW?Tu>HCI}hpl4NQ;TOymPJ7amT8Bq#Av0kl{1q{XBuy{mOrzSO@5}t?hmgOh zq8Lv3W)Zm29%qMrdm7rVf3$aCTj6_QsXbFKMU*>kP821PPQhZPlN9Cmhz9+9Bbu`$ zK>P$sf`^UGTD6Q-MrpvubuOyVFetUAS{$IXDLy`^S~4;NR8*>M$cqC&Eg0u zX>ix7=MP*)uO{H}XBp z)$`p9GSHrD1^S9!9Gl3d^i0_iCAPE65>`Ywba$|SuQuHja5~W4?1TZqt4j9 z+y7${hIM9J(;#uUOF~4x;n(qNv&1M9+aL94BIG$*WgoT6o0#U-ii~>F$7WO5F1;U@ z(vkaL1CZH+5g5>h>a@DNVXnK2O&w9X-fAC9Sxc?uBCuC|t0SrU{-0l`I3f;w5Lqpx zF)8eoZSMHxgeL>|#I$S75J{KkKszEp5D=hklF?oW>)3$!H04&(i~}ul0=U{MnCK5e zDMSRW=`l+wLLlxH1gDJ7zJ(yY*$~*fpG!?1Q8|KB#?>1G3UC-4fuo~MbK^jTp22`q zCa+6DXn3k%fC(k)m0>_Ag@A}BP4E>rC$MQ62v}3Lut)`jQV17tu1*tXMu;AT01OVt zAxuOlg~&nURHotA2+YRUcRiDR=|1#o`TM$%_0B_H-jDwb zCIoS+d}=`9^SVcrSa)C>kKWmvPw#mUBC?2pPE6GF`LZM(zpaNFK)5JZRq%tv0%Lfw zNyb6?`0LSn?0i{A_$#zrdPqLuhT?! zAVN`B>0Ne>%m1-!VV8&4BV1~NfK0R3)@8M7_H&mhnvVBj83(tC%+x{m#cO*Q?9Ju^DrUSnM{hCE`w&nEPwX`It2^51@!`9?*WQ7q-MYi0 zw*9(Oz6UXQXoQa*;x!qTLgC`{-WvEHcIfp98VO187Z%ZO}6itMk2&>HrXEFA;(z z-#4G9kAq4JnZT*0mwW2u@{b>qAynGO^X@?({ks_G*I%FgY4+jgg_@HaTne~FAK@%P z>^%tPiUs>N%pn!+dGm-jKI-Jw7x1c70xrVKzxAFr6@zJ+tPmco=h#u(reA^m{swSX ztF>7Wmo5NgkxMov1vj@Nfzc^NLD<*F9 z(b?KqHjOI{WNSr}+Iw2_FlB%)pDeEG8i`&8vaf#!{Z|7J?oOyNE0KD~Nm=paZ5s8F zI=?f5wQqy$EwL(Uwp<-yADy1g1G>Zl8u-_^4`IQ^H_EI7vEaN!5PokgI@Usf-d*&0 z!{w0>43ETL`x`o*cOXQO!FY3XCE;=?d_lXLE`>c)r`vIPUui_iJ?>OC0gzEqve#Y%ofjJ9gxF&Ec^+%uo#a{QiR{{* z0QkO;&>Okl6J2&$5RjzkPp$U^^ekCEaXJ=jMRvAY2L1)SF#F&n^n43^r5F9qd>Jft zOtpfw5XUxV%pFc$FMri>H zzQuehB<~MyIViJi-qCkH{VNCqCL|@?9*2cGqz&>}{W_X+w&B95AES%hmV2X;J_I(k zw_B4p1lRec=?RlylVgcwK)yqxDMJor0bKb4(bDRY^9U;M>LM4o0;MHyX>P|NT>SfO z^v`kWpX;L2fvW|^U9zXgWhz)ub|C>#GtL-i#`Yrsd^ExFQ+oT*)ZP&y05j=mEzRJr z+y`2!mPc3~+Q24zsll;^k#Be4V+vHpl7HOq3<873Q6Lp7>woNM%va3(NdcX74-elf8guKEUt=t z52R=am2lHzUVB15*x*RoaU!(W5?CA#4i2ba*V)ud%`vZel0=mwrLfzwWaTL9L^)52jo zj>mQ3mlVCNyQVNE29zcFIG@4(gy(OXKQilD?&fpWY`EHgL#XWPOA#rr%C2dl>(Y}Q zVK#BgAtN#_MidQ|kS1IO0_e^W2I_Rw{>N;-x4R2%H|&LRsQuthZh}zY^_4VLjtq*s z_fS;*S-MVp=b`hD16AL)-uxu6(gdUqi_L$DJLD z%>hbv(mf!| zMQmqZ{|{LFS*vf$pW7ap_ksLt|EFnq&Uo$Z`1Pe2YG5+?*g&n>Os&=LWS6Rkx#_~5 z&B#hO^u#!P<`6+v1^7V<`deX|j9j333;*QG#4Sg|(YQzI1ewWhADA@Lo-9HheojcV zVWAe_UgNDn>&6Bm`I5nF@gg&T)(8DVygKlJeCa+>YUCxhcK{vYEckr)@y_D~lflmo zfBq$vu?G5=L)@GbYqqLn6USm*Rw_i8f3>XXPJihydF;70Iy8IqM&LWy2+!`ji2ODy7E@qUYmT1nPba1YP+2^}O7T5X#6Db`lMN^keHedTunCj&TjJ zDz`(~o77nJs5UZMb1z75~OTbEdE-eG+T#h;GDSQn09mCJ@Q2-CNK}dtjIQGuY?uHN% zIrJ*dJChZqJ=t3E8+<9-Xyo@l@ua(>5Sgf%0lrxwSx=Im@|xcup*I!yeZ&MlFg=tX zqhMkuQE@}eYz20Iva`OSo&a}~W?eeWH(>p72%NT0f7|2TtQc zo3Znal%7`FDEo#v*W;bWGHLW#8X+_+ghEf63Kfrid7#QV!HzGw&OI~NOXKrUYm4e| zUWCm&dCI5JzVRC5f&EN5<0wmom)R^V@$BE+91SH@_RH=SDBIsGzTm)6z12(7!nbcS zKe&_0_x9y>a{c|QJI22yna=-_WbOHG-Zx!v;t*3dJXhd!D=ZBPb@zLFbKINs^fu3e zW7yl-b#mf`42pt!q6mRPEu*bqj9WmX_fc`x&LQD{vvw|{PLCAY3gy`QbJ0kO+ z5A8@zY3BDys_!>Y0YJ)JVUq{hi6pG{f5&SEGUcH`+B(>8(c1&HAgrNiodV`@XJGOF zO1d;b#x7gIV{}9BVR*pddO;lHkW(#}Ou2ZF<1t^~nJa)JJv0DP1`;Gq#xY09p{9lM zayM|olO!Nz?AG8Qaub^Y9>dkDr#A#n_8oM)6g7`x$yT{=GLSMJ3Gqqfd=G@2@5yv0 zM{WSoz~MN`!pup|_t=3K*DEqYR%VZ-$@v}uJ}Iz&c+^ZkNPJ!JKY^{x$I#zE09>in z`N~9o>(I@l0}=51|7wcm|A%Z6n=yg{JMz|PA$+pD&z2#m1-fEo$J`9*1i6QXmNBQ+ zS#z@ThdH)&!Y`I&GX|=)MEAZsXSE*t8ZDm0J?O=RLdFDx+bZS44LOdH)?3Hk3tpdu z3_?B@dNyeAwn>-}GVfTvYe-l-?jDRV-mQr_MT>rQ@Qv(kx8?|IQv3n;YkyIW;5~i3 z>M*sw`Porycf>oZ{XxOhmb*a07&d!4*JtLhXLXVHjKtG_atgNQC7Tv*rLnx25;wgp z`V}q-vYoRDtBj|FQesuvkhfI8Em)H%^7NS~KaP?~X4DG)y2tv@iCKw9o7S~e3Q5yu z?`*D^z#{9v2MR@sb`h8O&RQ#fYuj1cPAN>N8ehN7r(wH9WZC_vvQfw6ef&4E>dN`a zFG$U)6p2v31O6k?JqQ1kSxcp3{HwjD8*3e_97Xrd?=+7M<3=v7Vd{*n>c=w=FKavb zTg0Md8Z*|Ild0z|e(RMDNOu-%=b1#;_tL&7AvzHA;W^DXAHD0&_HK0^X5ltL5zeyv zrz(;`hZs>3e)hlJ^@S;;a zfuDbi#GM_p>egPIzU=j3ru*3?+_6<$@JRly`o}RX&wuJPYBnAR*#l6(c0*KrSktSz zKr-gr;6=gfArRAA>0dGt1ta^zrbte^dG1+nF&fkEx>oEkmBi0>hM?~UwIW#5UCvRf z`LEW8^U-u3=EW9sewXuy1PDIQOc7hL`&XMj4OXW-SM;T=-wVHYse4l`?y4ZWLX)t} z-mulijHB=06RH^F=n3th&)Ws@$kY(7{j~`FyAWARTPgdEPZD(EQGWm2wg|})6BW38 zpIFYB*R$NW!Kn7VIlZ!_f9$R!VV^mc5j&r>-L5=}NfrI`D|; zU&Y%ITg|y4Yc~T@hu;M z?>yPz8tsQv^Qx@Zj$2~!>pnWJ%i?V;H-2gU`^G{o%-eQ}sW3Eea^h3&;O=Xq#oOOL zUhGKAn_`flQ`ZrUj{HWlIOr~x$YBmRw>ounZ)iPV;W$AqT$hnQG^}MvJX^qqr_Y98e58=+~&Ax3Sd-(uZzIQ#F@^$u{XC;ioF*P=FJg~fwkLu&U^@d3) z5J})$(#mw*@vms>Z}{unIjWhG2B~X{QoRupk=wWIzW>b@a7pHngM^3`U3!$Y90N0lKQn zh$}^YLpD40#QEo^e($M5%!yM^(T^0VZ(Bx6nlwpQ#phUoS}YHqD5FJEaunAgPwx3u z1Mk-lG4hEzudEKb(=25f#4n@8O5M`?LK`;{_-%7nWcj&_@hvY?Ro7_UIb5+Lbz*1x zjUJN)eqYk3v^@4A>k{ny(o0tPOjDnxFRl9os+1nn`1D>I<~b9PDviAN^`e^kckx=y zGw+@G?o^?c)Sr56x^PJ^Ch@uUyvJIs2{n2zNu;`(vSxTHOKo3i zEFf|ibzym4_p{jmi2}Ci?&p4(hH?LGB!9P>ai_x$=2!y1NByd*eXy7!RV%m3EXUfv ztnH>z2hjsFT_B-3-#;bjl_4IDeULDbgZX`QVb~y|E$HceGyZ16zd;3NqHDZu{ee>l zW@^r^8BwGTsc1@wSu!45IgT1Ij6ik7a}+(>z27h-aNNDib0u6L@+;<13foS9g9t$; zfSOe@mAg|x%-}BS_7CVO$@K|(Q$wFv!9w^gS!+3+sMIOtwNxKLNU4dM&42OOEb*!M zS9`Au+TgbI;Ye%`?l~#GENr9Q(YZ7MO zp{mNtR7T-2u842u6o*o*9O1TJA#?)7V z>+hor%U5Be+%YcoLS>~N?-wt+JUkbbv_c5hl!F9ZdT?q+>FdiZ8BQvZlsg)NbU!Fp7^XALkyrHWIU zjC5BT~Oy)H~N9Wi1@ zg!g{x0{+74*b(M%TSiwdP_K;+QJ>om@8M*NfK7bbE*%Ystgf7n8GqFj@&MVRCxjWy zO{fwu4Ak=M*fq^+jVMJ?EsKZ^b5LYh4CkdAD_3`?)_#AiwIfDMrb}~_k|1%sj!CvI zLG^gBzOv8cZ+P(`r%jYtFEROPKo_IzpM^!ZzPcoM_>T}grN*h_XHy$iaFP-k%5vwV z!ITB=CF#5Iv2x`c>djin^3Bk*`q@Q|2AVeGOzdAK6(iE>T;mw6_gA84$ji7WKJ%&b<300%M-9LHbH1+sb z;YrB~RGe1%UZyNXQ5|o`gnV{%XId<@&m|35TFQo>+hz>qEJdXE<71EVZU|K-Hol6u z^>4i$hIRr3!@DPW%ub&yFqM7r!$&=ZHC_ery|}#q3n)stNQ`igm_~zMDX9U zu^>@u*;u$gtwL^UWs9YU2`PgR3Cm?|X^=W$cR;dVtNTjJo)|>^fjhXZwY8Fr900AD z2cUzaWRgF7ot4z(Xk@z-u(2` zREL;yqawYml9EkUcDBth4yPKG str: + """The name""" + return self._name + @property def hvac_action(self): """The hvac action of the mock climate""" @@ -244,6 +302,10 @@ class MockClimate(ClimateEntity): """The hvac mode""" self._attr_hvac_mode = hvac_mode + def set_hvac_mode(self, hvac_mode): + """The hvac mode""" + self._attr_hvac_mode = hvac_mode + def set_hvac_action(self, hvac_action: HVACAction): """Set the HVACaction""" self._attr_hvac_action = hvac_action @@ -350,6 +412,66 @@ class MagicMockClimate(MagicMock): return 19 +class MockSwitch(SwitchEntity): + """A fake switch to be used instead real switch""" + + def __init__( # pylint: disable=unused-argument, dangerous-default-value + self, hass: HomeAssistant, unique_id, name, entry_infos={} + ): + """Init the switch""" + super().__init__() + + self.hass = hass + self.platform = "switch" + self.entity_id = self.platform + "." + unique_id + self._name = name + self._attr_is_on = False + + @property + def name(self) -> str: + """The name""" + return self._name + + @overrides + def turn_on(self, **kwargs: Any): + """Turns the switch on and notify the state change""" + self._attr_is_on = True + # self.async_write_ha_state() + + @overrides + def turn_off(self, **kwargs: Any): + """Turns the switch on and notify the state change""" + self._attr_is_on = False + # self.async_write_ha_state() + + +class MockNumber(NumberEntity): + """A fake switch to be used instead real switch""" + + def __init__( # pylint: disable=unused-argument, dangerous-default-value + self, hass: HomeAssistant, unique_id, name, entry_infos={} + ): + """Init the switch""" + super().__init__() + + self.hass = hass + self.platform = "number" + self.entity_id = self.platform + "." + unique_id + self._name = name + self._attr_native_value = 0 + self._attr_native_min_value = 0 + + @property + def name(self) -> str: + """The name""" + return self._name + + @overrides + def set_native_value(self, value: float): + """Change the value""" + self._attr_native_value = value + + async def create_thermostat( hass: HomeAssistant, entry: MockConfigEntry, entity_id: str ) -> BaseThermostat: @@ -361,13 +483,6 @@ async def create_thermostat( await hass.config_entries.async_setup(entry.entry_id) assert entry.state is ConfigEntryState.LOADED - # def find_my_entity(entity_id) -> ClimateEntity: - # """Find my new entity""" - # component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN] - # for entity in component.entities: - # if entity.entity_id == entity_id: - # return entity - return search_entity(hass, entity_id, CLIMATE_DOMAIN) diff --git a/tests/conftest.py b/tests/conftest.py index 570a67c..d88b6b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,7 +29,11 @@ from custom_components.versatile_thermostat.config_flow import ( from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI from custom_components.versatile_thermostat.base_thermostat import BaseThermostat -from .commons import create_central_config +from .commons import ( + create_central_config, + FULL_CENTRAL_CONFIG, + FULL_CENTRAL_CONFIG_WITH_BOILER, +) pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name @@ -127,6 +131,16 @@ async def init_central_config_fixture( hass, init_vtherm_api ): # pylint: disable=unused-argument """Initialize the VTherm API""" - await create_central_config(hass) + await create_central_config(hass, FULL_CENTRAL_CONFIG) + + yield + + +@pytest.fixture(name="init_central_config_with_boiler_fixture") +async def init_central_config_with_boiler_fixture( + hass, init_vtherm_api +): # pylint: disable=unused-argument + """Initialize the VTherm API""" + await create_central_config(hass, FULL_CENTRAL_CONFIG_WITH_BOILER) yield diff --git a/tests/const.py b/tests/const.py index bb19197..5fe3c40 100644 --- a/tests/const.py +++ b/tests/const.py @@ -152,6 +152,7 @@ MOCK_WINDOW_AUTO_CONFIG = { CONF_WINDOW_AUTO_OPEN_THRESHOLD: 1.0, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.0, CONF_WINDOW_AUTO_MAX_DURATION: 5.0, + CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY, } MOCK_MOTION_CONFIG = { diff --git a/tests/test_central_boiler.py b/tests/test_central_boiler.py new file mode 100644 index 0000000..879bc14 --- /dev/null +++ b/tests/test_central_boiler.py @@ -0,0 +1,835 @@ +# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long + +""" Test the central_configuration """ +import asyncio +from datetime import datetime + +from unittest.mock import patch, call + +from homeassistant.core import HomeAssistant + +from homeassistant.config_entries import ConfigEntryState + +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.versatile_thermostat.thermostat_switch import ( + ThermostatOverSwitch, +) + +from custom_components.versatile_thermostat.thermostat_valve import ( + ThermostatOverValve, +) + +from custom_components.versatile_thermostat.thermostat_climate import ( + ThermostatOverClimate, +) + +from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI +from custom_components.versatile_thermostat.binary_sensor import ( + CentralBoilerBinarySensor, +) + +from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import +from .const import * # pylint: disable=wildcard-import, unused-wildcard-import + + +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_add_a_central_config_with_boiler( + hass: HomeAssistant, + skip_hass_states_is_state, +): + """Tests the clean_central_config_doubon of base_thermostat""" + central_config_entry = MockConfigEntry( + domain=DOMAIN, + title="TheCentralConfigMockName", + unique_id="centralConfigUniqueId", + data=FULL_CENTRAL_CONFIG_WITH_BOILER, + ) + + central_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(central_config_entry.entry_id) + assert central_config_entry.state is ConfigEntryState.LOADED + + entity: ThermostatOverClimate = search_entity( + hass, "climate.thecentralconfigmockname", "climate" + ) + + assert entity is None + + assert count_entities(hass, "climate.thecentralconfigmockname", "climate") == 0 + + # Test that VTherm API find the CentralConfig + api = VersatileThermostatAPI.get_vtherm_api(hass) + central_configuration = api.find_central_configuration() + assert central_configuration is not None + + # Test that VTherm API have any central boiler entities + assert api.nb_active_device_for_boiler_entity is not None + assert api.nb_active_device_for_boiler == 0 + + assert api.nb_active_device_for_boiler_threshold_entity is not None + assert api.nb_active_device_for_boiler_threshold is 1 # the default value is 1 + + +async def test_update_central_boiler_state_simple( + hass: HomeAssistant, + # skip_hass_states_is_state, + init_central_config_with_boiler_fixture, +): + """Test that the central boiler state behavoir""" + + api = VersatileThermostatAPI.get_vtherm_api(hass) + + switch1 = MockSwitch(hass, "switch1", "theSwitch1") + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverSwitchMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverSwitchMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 8, + CONF_TEMP_MAX: 18, + "frost_temp": 10, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 21, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_HEATER: switch1.entity_id, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_INVERSE_SWITCH: False, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.01, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1, + CONF_USE_MAIN_CENTRAL_CONFIG: True, + CONF_USE_TPI_CENTRAL_CONFIG: True, + CONF_USE_PRESETS_CENTRAL_CONFIG: True, + CONF_USE_ADVANCED_CENTRAL_CONFIG: True, + CONF_USED_BY_CENTRAL_BOILER: True, + }, + ) + + entity: ThermostatOverSwitch = await create_thermostat( + hass, entry, "climate.theoverswitchmockname" + ) + assert entity + assert entity.name == "TheOverSwitchMockName" + assert entity.is_over_switch + assert entity.underlying_entities[0].entity_id == "switch.switch1" + + assert api.nb_active_device_for_boiler_threshold == 1 + assert api.nb_active_device_for_boiler == 0 + + # Force the VTherm to heat + await entity.async_set_hvac_mode(HVACMode.HEAT) + await entity.async_set_preset_mode(PRESET_BOOST) + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + await send_temperature_change_event(entity, 10, now) + + assert entity.hvac_mode == HVACMode.HEAT + + boiler_binary_sensor: CentralBoilerBinarySensor = search_entity( + hass, "binary_sensor.central_boiler", "binary_sensor" + ) + assert boiler_binary_sensor is not None + assert boiler_binary_sensor.state == STATE_OFF + + # 1. start a heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch1.async_turn_on() + switch1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ), + ] + ) + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": True}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 1 + assert boiler_binary_sensor.state == STATE_ON + + # 2. stop a heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch1.async_turn_off() + switch1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.IDLE + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call( + "switch", + "turn_off", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ) + ] + ) + + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": False}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 0 + assert boiler_binary_sensor.state == STATE_OFF + + entity.remove_thermostat() + + +async def test_update_central_boiler_state_multiple( + hass: HomeAssistant, + # skip_hass_states_is_state, + init_central_config_with_boiler_fixture, +): + """Test that the central boiler state behavoir""" + + api = VersatileThermostatAPI.get_vtherm_api(hass) + + switch1 = MockSwitch(hass, "switch1", "theSwitch1") + switch2 = MockSwitch(hass, "switch2", "theSwitch2") + switch3 = MockSwitch(hass, "switch3", "theSwitch3") + switch4 = MockSwitch(hass, "switch4", "theSwitch4") + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverSwitchMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverSwitchMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 8, + CONF_TEMP_MAX: 18, + "frost_temp": 10, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 21, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_HEATER: switch1.entity_id, + CONF_HEATER_2: switch2.entity_id, + CONF_HEATER_3: switch3.entity_id, + CONF_HEATER_4: switch4.entity_id, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_INVERSE_SWITCH: False, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.01, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1, + CONF_USE_MAIN_CENTRAL_CONFIG: True, + CONF_USE_TPI_CENTRAL_CONFIG: True, + CONF_USE_PRESETS_CENTRAL_CONFIG: True, + CONF_USE_ADVANCED_CENTRAL_CONFIG: True, + CONF_USED_BY_CENTRAL_BOILER: True, + }, + ) + + entity: ThermostatOverSwitch = await create_thermostat( + hass, entry, "climate.theoverswitchmockname" + ) + assert entity + assert entity.name == "TheOverSwitchMockName" + assert entity.is_over_switch + assert entity.underlying_entities[0].entity_id == "switch.switch1" + assert entity.underlying_entities[1].entity_id == "switch.switch2" + assert entity.underlying_entities[2].entity_id == "switch.switch3" + assert entity.underlying_entities[3].entity_id == "switch.switch4" + + assert api.nb_active_device_for_boiler_threshold == 1 + assert api.nb_active_device_for_boiler == 0 + # Force the VTherm to heat + await entity.async_set_hvac_mode(HVACMode.HEAT) + await entity.async_set_preset_mode(PRESET_BOOST) + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + await send_temperature_change_event(entity, 10, now) + + assert entity.hvac_mode == HVACMode.HEAT + + # 0. set threshold to 3 + api.nb_active_device_for_boiler_threshold_entity.set_native_value(3) + assert api.nb_active_device_for_boiler_threshold == 3 + + boiler_binary_sensor: CentralBoilerBinarySensor = search_entity( + hass, "binary_sensor.central_boiler", "binary_sensor" + ) + assert boiler_binary_sensor is not None + assert boiler_binary_sensor.state == STATE_OFF + + # 1. start a first heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch1.async_turn_on() + switch1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + assert mock_service_call.call_count == 1 + # No switch of the boiler + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + {"entity_id": "switch.switch1"}, + ), + ] + ) + assert mock_send_event.call_count == 0 + + assert api.nb_active_device_for_boiler == 1 + assert boiler_binary_sensor.state == STATE_OFF + + # 2. start a 2nd heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch2.async_turn_on() + switch2.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + # Only the first heater is started by the algo + assert mock_service_call.call_count == 1 + # No switch of the boiler. Caution: each time a underlying heater state change itself, + # the cycle restarts. So it is always the first heater that is started + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + {"entity_id": "switch.switch1"}, + ), + ] + ) + assert mock_send_event.call_count == 0 + + assert api.nb_active_device_for_boiler == 2 + assert boiler_binary_sensor.state == STATE_OFF + + # 3. start a 3rd heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch3.async_turn_on() + switch3.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + # Only the first heater is started by the algo + assert mock_service_call.call_count == 2 + # No switch of the boiler. Caution: each time a underlying heater state change itself, + # the cycle restarts. So it is always the first heater that is started + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ), + call.service_call( + "switch", + "turn_on", + {"entity_id": "switch.switch1"}, + ), + ], + any_order=True, + ) + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": True}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 3 + assert boiler_binary_sensor.state == STATE_ON + + # 4. start a 4th heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch4.async_turn_on() + switch4.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + # Only the first heater is started by the algo + assert mock_service_call.call_count == 1 + # No switch of the boiler. Caution: each time a underlying heater state change itself, + # the cycle restarts. So it is always the first heater that is started + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + {"entity_id": "switch.switch1"}, + ), + ] + ) + assert mock_send_event.call_count == 0 + assert api.nb_active_device_for_boiler == 4 + assert boiler_binary_sensor.state == STATE_ON + + # 5. stop a heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch1.async_turn_off() + switch1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + assert mock_service_call.call_count == 0 + assert mock_send_event.call_count == 0 + assert api.nb_active_device_for_boiler == 3 + assert boiler_binary_sensor.state == STATE_ON + + # 6. stop a 2nd heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await switch4.async_turn_off() + switch4.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call( + "switch", + "turn_off", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ) + ] + ) + + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": False}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 2 + assert boiler_binary_sensor.state == STATE_OFF + + entity.remove_thermostat() + + +async def test_update_central_boiler_state_simple_valve( + hass: HomeAssistant, + # skip_hass_states_is_state, + init_central_config_with_boiler_fixture, +): + """Test that the central boiler state behavoir""" + + api = VersatileThermostatAPI.get_vtherm_api(hass) + + valve1 = MockNumber(hass, "valve1", "theValve1") + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverValveMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverValveMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_VALVE, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 8, + CONF_TEMP_MAX: 18, + "frost_temp": 10, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 21, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_VALVE: valve1.entity_id, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_INVERSE_SWITCH: False, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.01, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1, + CONF_USE_MAIN_CENTRAL_CONFIG: True, + CONF_USE_TPI_CENTRAL_CONFIG: True, + CONF_USE_PRESETS_CENTRAL_CONFIG: True, + CONF_USE_ADVANCED_CENTRAL_CONFIG: True, + CONF_USED_BY_CENTRAL_BOILER: True, + }, + ) + + entity: ThermostatOverValve = await create_thermostat( + hass, entry, "climate.theovervalvemockname" + ) + assert entity + assert entity.name == "TheOverValveMockName" + assert entity.is_over_valve + assert entity.underlying_entities[0].entity_id == "number.valve1" + + assert api.nb_active_device_for_boiler_threshold == 1 + assert api.nb_active_device_for_boiler == 0 + + # Force the VTherm to heat + await entity.async_set_hvac_mode(HVACMode.HEAT) + await entity.async_set_preset_mode(PRESET_BOOST) + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + + assert entity.hvac_mode == HVACMode.HEAT + + boiler_binary_sensor: CentralBoilerBinarySensor = search_entity( + hass, "binary_sensor.central_boiler", "binary_sensor" + ) + assert boiler_binary_sensor is not None + assert boiler_binary_sensor.state == STATE_OFF + + # 1. start a valve + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await send_temperature_change_event(entity, 10, now) + # we have to simulate the valve also else the test don't work + valve1.set_native_value(10) + valve1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ), + ] + ) + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": True}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 1 + assert boiler_binary_sensor.state == STATE_ON + + # 2. stop a heater + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await send_temperature_change_event(entity, 25, now) + # Change the valve value to 0 + valve1.set_native_value(0) + valve1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.IDLE + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call( + "switch", + "turn_off", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ) + ] + ) + + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": False}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 0 + assert boiler_binary_sensor.state == STATE_OFF + + entity.remove_thermostat() + + +async def test_update_central_boiler_state_simple_climate( + hass: HomeAssistant, + # skip_hass_states_is_state, + init_central_config_with_boiler_fixture, +): + """Test that the central boiler state behavoir""" + + api = VersatileThermostatAPI.get_vtherm_api(hass) + + climate1 = MockClimate(hass, "climate1", "theClimate1") + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverClimateMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverClimateMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 8, + CONF_TEMP_MAX: 18, + "frost_temp": 10, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 21, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_CLIMATE: climate1.entity_id, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1, + CONF_USE_MAIN_CENTRAL_CONFIG: True, + CONF_USE_PRESETS_CENTRAL_CONFIG: True, + CONF_USE_ADVANCED_CENTRAL_CONFIG: True, + CONF_USED_BY_CENTRAL_BOILER: True, + }, + ) + + with patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", + return_value=climate1, + ): + entity: ThermostatOverValve = await create_thermostat( + hass, entry, "climate.theoverclimatemockname" + ) + assert entity + assert entity.name == "TheOverClimateMockName" + assert entity.is_over_climate + assert entity.underlying_entities[0].entity_id == "climate.climate1" + + assert api.nb_active_device_for_boiler_threshold == 1 + assert api.nb_active_device_for_boiler == 0 + + # Force the VTherm to heat + await entity.async_set_hvac_mode(HVACMode.HEAT) + await entity.async_set_preset_mode(PRESET_BOOST) + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + + assert entity.hvac_mode == HVACMode.HEAT + + boiler_binary_sensor: CentralBoilerBinarySensor = search_entity( + hass, "binary_sensor.central_boiler", "binary_sensor" + ) + assert boiler_binary_sensor is not None + assert boiler_binary_sensor.state == STATE_OFF + + # 1. start a climate + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await send_temperature_change_event(entity, 10, now) + # we have to simulate the climate also else the test don't work + climate1.set_hvac_mode(HVACMode.HEAT) + climate1.set_hvac_action(HVACAction.HEATING) + climate1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.HEATING + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ), + ] + ) + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": True}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 1 + assert boiler_binary_sensor.state == STATE_ON + + # 2. stop a climate + with patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call, patch( + "custom_components.versatile_thermostat.binary_sensor.send_vtherm_event" + ) as mock_send_event: + await send_temperature_change_event(entity, 25, now) + climate1.set_hvac_mode(HVACMode.HEAT) + climate1.set_hvac_action(HVACAction.IDLE) + climate1.async_write_ha_state() + # Wait for state event propagation + await asyncio.sleep(0.1) + + assert entity.hvac_action == HVACAction.IDLE + + assert mock_service_call.call_count >= 1 + mock_service_call.assert_has_calls( + [ + call( + "switch", + "turn_off", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ) + ] + ) + + assert mock_send_event.call_count >= 1 + mock_send_event.assert_has_calls( + [ + call.send_vtherm_event( + hass=hass, + event_type=EventType.CENTRAL_BOILER_EVENT, + entity=api.central_boiler_entity, + data={"central_boiler": False}, + ) + ] + ) + + assert api.nb_active_device_for_boiler == 0 + assert boiler_binary_sensor.state == STATE_OFF + + entity.remove_thermostat() diff --git a/tests/test_central_config.py b/tests/test_central_config.py index 8fb9ebf..a772bd1 100644 --- a/tests/test_central_config.py +++ b/tests/test_central_config.py @@ -31,8 +31,8 @@ from .commons import * # pylint: disable=wildcard-import, unused-wildcard-impor from .const import * # pylint: disable=wildcard-import, unused-wildcard-import -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -@pytest.mark.parametrize("expected_lingering_timers", [True]) +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_state): """Tests the clean_central_config_doubon of base_thermostat""" central_config_entry = MockConfigEntry( @@ -76,6 +76,7 @@ async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_sta CONF_SECURITY_DELAY_MIN: 61, CONF_SECURITY_MIN_ON_PERCENT: 0.5, CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2, + CONF_ADD_CENTRAL_BOILER_CONTROL: False, }, ) @@ -96,9 +97,16 @@ async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_sta central_configuration = api.find_central_configuration() assert central_configuration is not None + # Test that VTherm API doesn't have any central boiler entities + assert api.nb_active_device_for_boiler_entity is None + assert api.nb_active_device_for_boiler is None -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -@pytest.mark.parametrize("expected_lingering_timers", [True]) + assert api.nb_active_device_for_boiler_threshold_entity is None + assert api.nb_active_device_for_boiler_threshold is None + + +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_minimal_over_switch_wo_central_config( hass: HomeAssistant, skip_hass_states_is_state, init_vtherm_api ): @@ -172,9 +180,11 @@ async def test_minimal_over_switch_wo_central_config( assert entity._security_default_on_percent == 0.1 assert entity.is_inversed + entity.remove_thermostat() -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -@pytest.mark.parametrize("expected_lingering_timers", [True]) + +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_full_over_switch_wo_central_config( hass: HomeAssistant, skip_hass_states_is_state, init_vtherm_api ): @@ -286,9 +296,11 @@ async def test_full_over_switch_wo_central_config( assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor" + entity.remove_thermostat() -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -@pytest.mark.parametrize("expected_lingering_timers", [True]) + +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_full_over_switch_with_central_config( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): @@ -396,9 +408,11 @@ async def test_full_over_switch_with_central_config( assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor" + entity.remove_thermostat() -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -@pytest.mark.parametrize("expected_lingering_timers", [True]) + +# @pytest.mark.parametrize("expected_lingering_tasks", [True]) +# @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_over_switch_with_central_config_but_no_central_config( hass: HomeAssistant, skip_hass_states_get, init_vtherm_api ): diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index d04f613..b27c088 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,7 @@ 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_USED_BY_CENTRAL_BOILER: False, } assert result["result"] assert result["result"].domain == DOMAIN @@ -287,6 +290,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 +377,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 +517,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 diff --git a/tests/test_valve.py b/tests/test_valve.py index 47593fc..f9d23b3 100644 --- a/tests/test_valve.py +++ b/tests/test_valve.py @@ -321,9 +321,6 @@ async def test_over_valve_full_start( await try_condition(None) assert entity.hvac_mode is HVACMode.HEAT - assert ( - entity.hvac_action is HVACAction.OFF - or entity.hvac_action is HVACAction.IDLE - ) + assert entity.hvac_action is HVACAction.HEATING assert entity.target_temperature == 17.1 # eco assert entity.valve_open_percent == 10 diff --git a/tests/test_window.py b/tests/test_window.py index 441d470..e43472f 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -6,6 +6,9 @@ from unittest.mock import patch, call, PropertyMock from datetime import datetime, timedelta from custom_components.versatile_thermostat.base_thermostat import BaseThermostat +from custom_components.versatile_thermostat.thermostat_climate import ( + ThermostatOverClimate, +) from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import logging.getLogger().setLevel(logging.DEBUG) @@ -46,6 +49,7 @@ async def test_window_management_time_not_enough( CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor", CONF_WINDOW_DELAY: 0, # important to not been obliged to wait + CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF, }, ) @@ -134,6 +138,7 @@ async def test_window_management_time_enough( CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor", CONF_WINDOW_DELAY: 0, # important to not been obliged to wait + CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF, }, ) @@ -242,7 +247,7 @@ async def test_window_management_time_enough( @pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state): - """Test the Window management""" + """Test the auto Window management with fast slope down""" entry = MockConfigEntry( domain=DOMAIN, @@ -822,7 +827,6 @@ async def test_window_auto_no_on_percent( entity.remove_thermostat() -# PR - Adding Window Bypass @pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state): @@ -1207,3 +1211,694 @@ async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is # Clean the entity entity.remove_thermostat() + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_state): + """Test the Window management with the fan_only option""" + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverClimateMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverClimateMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 19, + CONF_USE_WINDOW_FEATURE: True, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_CLIMATE: "climate.mock_climate", + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor", + CONF_WINDOW_DELAY: 1, + CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY, + # CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6, + # CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6, + # CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection + }, + ) + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + + fake_underlying_climate = MockClimate( + hass=hass, + unique_id="mockUniqueId", + name="MockClimateName", + hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.FAN_ONLY], + ) + + # 1. intialize climate entity + with patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", + return_value=fake_underlying_climate, + ): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state is ConfigEntryState.LOADED + + entity: ThermostatOverClimate = search_entity( + hass, "climate.theoverclimatemockname", "climate" + ) + + assert entity + + assert entity.is_over_climate is True + assert entity.window_action == CONF_WINDOW_FAN_ONLY + + await entity.async_set_hvac_mode(HVACMode.HEAT) + assert entity.hvac_mode == HVACMode.HEAT + await entity.async_set_preset_mode(PRESET_COMFORT) + assert entity.preset_mode == PRESET_COMFORT + assert entity.target_temperature == 18 + + assert entity.window_state is STATE_OFF + + # 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "homeassistant.helpers.condition.state", return_value=True + ), patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode" + ) as mock_underlying_set_hvac_mode: + event_timestamp = now - timedelta(minutes=2) + try_window_condition = await send_window_change_event( + entity, True, False, event_timestamp + ) + await try_window_condition(None) + + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.FAN_ONLY} + ) + ] + ) + + # The underlying should be in OFF hvac_mode + assert mock_underlying_set_hvac_mode.call_count == 1 + mock_underlying_set_hvac_mode.assert_has_calls( + [ + call.set_hvac_mode(HVACMode.FAN_ONLY), + ] + ) + + assert entity.window_state == STATE_ON + # The underlying should be in FAN_ONLY hvac_mode + assert entity.hvac_mode is HVACMode.FAN_ONLY + assert entity._saved_hvac_mode is HVACMode.HEAT + assert entity.preset_mode is PRESET_COMFORT + + # 3. Close the window + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "homeassistant.helpers.condition.state", return_value=True + ), patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode" + ) as mock_underlying_set_hvac_mode: + event_timestamp = now - timedelta(minutes=1) + try_function = await send_window_change_event( + entity, False, True, event_timestamp, sleep=False + ) + + await try_function(None) + + # Wait for initial delay of heater + await asyncio.sleep(0.3) + + assert entity.window_state == STATE_OFF + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT} + ), + ], + any_order=False, + ) + + # The underlying should be in OFF hvac_mode + assert mock_underlying_set_hvac_mode.call_count == 1 + mock_underlying_set_hvac_mode.assert_has_calls( + [ + call.set_hvac_mode(HVACMode.HEAT), + ] + ) + assert entity.hvac_mode is HVACMode.HEAT + assert entity.preset_mode is PRESET_COMFORT + + # Clean the entity + entity.remove_thermostat() + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_window_action_fan_only_ko( + hass: HomeAssistant, skip_hass_states_is_state +): + """Test the Window management with the fan_only option but the underlyings doesn't have the FAN_ONLY mode + So the VTherm switch to OFF which is the fallback mode""" + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverClimateMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverClimateMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 19, + CONF_USE_WINDOW_FEATURE: True, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_CLIMATE: "climate.mock_climate", + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor", + CONF_WINDOW_DELAY: 1, + CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY, + # CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6, + # CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6, + # CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection + }, + ) + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + + fake_underlying_climate = MockClimate( + hass=hass, + unique_id="mockUniqueId", + name="MockClimateName", + hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO], + ) + + # 1. intialize climate entity + with patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", + return_value=fake_underlying_climate, + ): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state is ConfigEntryState.LOADED + + entity: ThermostatOverClimate = search_entity( + hass, "climate.theoverclimatemockname", "climate" + ) + + assert entity + + assert entity.is_over_climate is True + assert entity.window_action == CONF_WINDOW_FAN_ONLY + + await entity.async_set_hvac_mode(HVACMode.HEAT) + assert entity.hvac_mode == HVACMode.HEAT + await entity.async_set_preset_mode(PRESET_COMFORT) + assert entity.preset_mode == PRESET_COMFORT + assert entity.target_temperature == 18 + + assert entity.window_state is STATE_OFF + + # 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "homeassistant.helpers.condition.state", return_value=True + ), patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode" + ) as mock_underlying_set_hvac_mode: + event_timestamp = now - timedelta(minutes=2) + try_window_condition = await send_window_change_event( + entity, True, False, event_timestamp + ) + await try_window_condition(None) + + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})] + ) + + assert entity.window_state == STATE_ON + assert entity.hvac_mode is HVACMode.OFF + # The underlying should be in OFF hvac_mode + assert mock_underlying_set_hvac_mode.call_count == 1 + mock_underlying_set_hvac_mode.assert_has_calls( + [ + call.set_hvac_mode(HVACMode.OFF), + ] + ) + + assert entity._saved_hvac_mode is HVACMode.HEAT + assert entity.preset_mode is PRESET_COMFORT + + # 3. Close the window + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "homeassistant.helpers.condition.state", return_value=True + ), patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode" + ) as mock_underlying_set_hvac_mode: + event_timestamp = now - timedelta(minutes=1) + try_function = await send_window_change_event( + entity, False, True, event_timestamp, sleep=False + ) + + await try_function(None) + + # Wait for initial delay of heater + await asyncio.sleep(0.3) + + assert entity.window_state == STATE_OFF + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT} + ), + ], + any_order=False, + ) + + # The underlying should be in OFF hvac_mode + assert mock_underlying_set_hvac_mode.call_count == 1 + mock_underlying_set_hvac_mode.assert_has_calls( + [ + call.set_hvac_mode(HVACMode.HEAT), + ] + ) + assert entity.hvac_mode is HVACMode.HEAT + assert entity.preset_mode is PRESET_COMFORT + + # Clean the entity + entity.remove_thermostat() + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_state): + """Test the Window management with the eco_temp option""" + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverSwitchMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverSwitchMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 21, + CONF_USE_WINDOW_FEATURE: True, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_HEATER: "switch.mock_switch", + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.01, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1, + CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1, + CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test + CONF_WINDOW_ACTION: CONF_WINDOW_ECO_TEMP, + }, + ) + + entity: BaseThermostat = await create_thermostat( + hass, entry, "climate.theoverswitchmockname" + ) + assert entity + + tz = get_tz(hass) # pylint: disable=invalid-name + now = datetime.now(tz) + + await entity.async_set_hvac_mode(HVACMode.HEAT) + await entity.async_set_preset_mode(PRESET_BOOST) + assert entity.hvac_mode is HVACMode.HEAT + assert entity.preset_mode is PRESET_BOOST + assert entity.overpowering_state is None + assert entity.target_temperature == 21 + + assert entity.window_state is STATE_OFF + assert entity.is_window_auto_enabled is True + + # 1. Initialize the slope algo with 2 measurements + event_timestamp = now + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + + # 2. Make the temperature down + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + return_value=True, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 0 + assert entity.is_device_active is True + assert entity.hvac_mode is HVACMode.HEAT + assert entity.window_state is STATE_OFF + assert entity.window_auto_state is STATE_OFF + + # 3. send one degre down in one minute + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + return_value=True, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 18, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 1 + assert mock_heater_on.call_count == 0 + assert mock_heater_off.call_count == 0 + assert entity.last_temperature_slope == -6.24 + assert entity.window_auto_state == STATE_ON + assert entity.window_state == STATE_OFF + # No change on HVACMode + assert entity.hvac_mode is HVACMode.HEAT + # No change on preset + assert entity.preset_mode is PRESET_BOOST + # The eco temp + assert entity.target_temperature == 17 + + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.WINDOW_AUTO_EVENT, + {"type": "start", "cause": "slope alert", "curve_slope": -6.24}, + ), + ], + any_order=True, + ) + + # 4. send another 0.1 degre in one minute -> no change + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + new_callable=PropertyMock, + return_value=False, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 17.9, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 0 + assert mock_heater_on.call_count == 0 + assert mock_heater_off.call_count == 0 + assert round(entity.last_temperature_slope, 3) == -7.49 + assert entity.window_auto_state == STATE_ON + assert entity.hvac_mode is HVACMode.HEAT + # No change on preset + assert entity.preset_mode is PRESET_BOOST + # The eco temp + assert entity.target_temperature == 17 + + # 5. send another plus 1.1 degre in one minute -> restore state + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + new_callable=PropertyMock, + return_value=False, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.WINDOW_AUTO_EVENT, + { + "type": "end", + "cause": "end of slope alert", + "curve_slope": 0.42, + }, + ), + ], + any_order=True, + ) + assert mock_heater_on.call_count == 0 + assert mock_heater_off.call_count == 0 + assert entity.last_temperature_slope == 0.42 + assert entity.window_auto_state == STATE_OFF + assert entity.hvac_mode is HVACMode.HEAT + # No change on preset + assert entity.preset_mode is PRESET_BOOST + # The eco temp + assert entity.target_temperature == 21 + + # Clean the entity + entity.remove_thermostat() + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is_state): + """Test the Window management with the frost_temp option""" + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverSwitchMockName", + unique_id="uniqueId", + data={ + CONF_NAME: "TheOverSwitchMockName", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + "eco_temp": 17, + "comfort_temp": 18, + "boost_temp": 21, + "frost_temp": 10, + CONF_USE_WINDOW_FEATURE: True, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: False, + CONF_HEATER: "switch.mock_switch", + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_TPI_COEF_INT: 0.3, + CONF_TPI_COEF_EXT: 0.01, + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1, + CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1, + CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test + CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP, + }, + ) + + entity: BaseThermostat = await create_thermostat( + hass, entry, "climate.theoverswitchmockname" + ) + assert entity + + tz = get_tz(hass) # pylint: disable=invalid-name + now = datetime.now(tz) + + await entity.async_set_hvac_mode(HVACMode.HEAT) + await entity.async_set_preset_mode(PRESET_BOOST) + assert entity.hvac_mode is HVACMode.HEAT + assert entity.preset_mode is PRESET_BOOST + assert entity.overpowering_state is None + assert entity.target_temperature == 21 + + assert entity.window_state is STATE_OFF + assert entity.is_window_auto_enabled is True + + # 1. Initialize the slope algo with 2 measurements + event_timestamp = now + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + + # 2. Make the temperature down + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + return_value=True, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 0 + assert entity.is_device_active is True + assert entity.hvac_mode is HVACMode.HEAT + assert entity.window_state is STATE_OFF + assert entity.window_auto_state is STATE_OFF + + # 3. send one degre down in one minute + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + return_value=True, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 18, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 1 + assert mock_heater_on.call_count == 0 + assert mock_heater_off.call_count == 0 + assert entity.last_temperature_slope == -6.24 + assert entity.window_auto_state == STATE_ON + assert entity.window_state == STATE_OFF + # No change on HVACMode + assert entity.hvac_mode is HVACMode.HEAT + # No change on preset + assert entity.preset_mode is PRESET_BOOST + # The eco temp + assert entity.target_temperature == 10 + + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.WINDOW_AUTO_EVENT, + {"type": "start", "cause": "slope alert", "curve_slope": -6.24}, + ), + ], + any_order=True, + ) + + # 4. send another 0.1 degre in one minute -> no change + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + new_callable=PropertyMock, + return_value=False, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 17.9, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 0 + assert mock_heater_on.call_count == 0 + assert mock_heater_off.call_count == 0 + assert round(entity.last_temperature_slope, 3) == -7.49 + assert entity.window_auto_state == STATE_ON + assert entity.hvac_mode is HVACMode.HEAT + # No change on preset + assert entity.preset_mode is PRESET_BOOST + # The eco temp + assert entity.target_temperature == 10 + + # 5. send another plus 1.1 degre in one minute -> restore state + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ) as mock_send_event, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on" + ) as mock_heater_on, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off" + ) as mock_heater_off, patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", + new_callable=PropertyMock, + return_value=False, + ): + event_timestamp = event_timestamp + timedelta(minutes=1) + await send_temperature_change_event(entity, 19, event_timestamp) + + # The heater turns on + assert mock_send_event.call_count == 1 + mock_send_event.assert_has_calls( + [ + call.send_event( + EventType.WINDOW_AUTO_EVENT, + { + "type": "end", + "cause": "end of slope alert", + "curve_slope": 0.42, + }, + ), + ], + any_order=True, + ) + assert mock_heater_on.call_count == 0 + assert mock_heater_off.call_count == 0 + assert entity.last_temperature_slope == 0.42 + assert entity.window_auto_state == STATE_OFF + assert entity.hvac_mode is HVACMode.HEAT + # No change on preset + assert entity.preset_mode is PRESET_BOOST + # The eco temp + assert entity.target_temperature == 21 + + # Clean the entity + entity.remove_thermostat()