Compare commits
3 Commits
main
...
3.6.0.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
954fc6271c | ||
|
|
6dc078871d | ||
|
|
cd0ab3c88d |
4
.bashrc
4
.bashrc
@@ -1,6 +1,4 @@
|
|||||||
|
|
||||||
echo "Sourcing .bashrc"
|
echo "Sourcing .bashrc"
|
||||||
alias ll='ls -l'
|
alias ll='ls -l'
|
||||||
export HA='/home/vscode/core'
|
# source venv/bin/activate
|
||||||
cd $HA
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|||||||
@@ -1,43 +1,54 @@
|
|||||||
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
|
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
|
||||||
// "image": "ghcr.io/ludeeus/devcontainer/integration:latest",
|
// "image": "ghcr.io/ludeeus/devcontainer/integration:latest",
|
||||||
{
|
{
|
||||||
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.10",
|
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
|
||||||
"name": "Versatile Thermostat integration",
|
"name": "Versatile Thermostat integration",
|
||||||
"context": "..",
|
|
||||||
"appPort": [
|
"appPort": [
|
||||||
"9123:8123"
|
"9123:8123"
|
||||||
],
|
],
|
||||||
// "postCreateCommand": "container install",
|
// "postCreateCommand": "container install",
|
||||||
"postCreateCommand": "./container install",
|
"postCreateCommand": "./container dev-setup",
|
||||||
"extensions": [
|
|
||||||
"ms-python.python",
|
|
||||||
"github.vscode-pull-request-github",
|
|
||||||
"ryanluker.vscode-coverage-gutters",
|
|
||||||
"ms-python.vscode-pylance"
|
|
||||||
],
|
|
||||||
"mounts": [
|
"mounts": [
|
||||||
"source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=/home/vscode/core/config/configuration.yaml,type=bind,consistency=cached",
|
"source=/Users/jmcollin/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
|
||||||
"source=${localWorkspaceFolder}/custom_components,target=/home/vscode/core/config/custom_components,type=bind,consistency=cached"
|
|
||||||
],
|
],
|
||||||
"settings": {
|
|
||||||
"files.eol": "\n",
|
"customizations": {
|
||||||
"editor.tabSize": 4,
|
"vscode": {
|
||||||
"terminal.integrated.profiles.linux": {
|
"extensions": [
|
||||||
"Bash Profile": {
|
"ms-python.python",
|
||||||
"path": "bash",
|
"github.vscode-pull-request-github",
|
||||||
"args": []
|
"ryanluker.vscode-coverage-gutters",
|
||||||
|
"ms-python.vscode-pylance"
|
||||||
|
],
|
||||||
|
// "mounts": [
|
||||||
|
// "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=/home/vscode/core/config/configuration.yaml,type=bind,consistency=cached",
|
||||||
|
// "source=${localWorkspaceFolder}/custom_components,target=/home/vscode/core/config/custom_components,type=bind,consistency=cached"
|
||||||
|
// ],
|
||||||
|
"settings": {
|
||||||
|
"files.eol": "\n",
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"terminal.integrated.profiles.linux": {
|
||||||
|
"bash": {
|
||||||
|
"path": "bash",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"terminal.integrated.defaultProfile.linux": "bash",
|
||||||
|
// "terminal.integrated.shell.linux": "/bin/bash",
|
||||||
|
"python.pythonPath": "/usr/bin/python3",
|
||||||
|
"python.analysis.autoSearchPaths": true,
|
||||||
|
"python.linting.pylintEnabled": true,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.formatting.provider": "black",
|
||||||
|
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnType": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"python.experiments.optOutFrom": ["pythonTestAdapter"],
|
||||||
|
"python.analysis.logLevel": "Trace"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"terminal.integrated.defaultProfile.linux": "Bash Profile",
|
|
||||||
// "terminal.integrated.shell.linux": "/bin/bash",
|
|
||||||
"python.pythonPath": "/usr/bin/python3",
|
|
||||||
"python.analysis.autoSearchPaths": true,
|
|
||||||
"python.linting.pylintEnabled": true,
|
|
||||||
"python.linting.enabled": true,
|
|
||||||
"python.formatting.provider": "black",
|
|
||||||
"editor.formatOnPaste": false,
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnType": true,
|
|
||||||
"files.trimTrailingWhitespace": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -106,3 +106,5 @@ dist
|
|||||||
# init file required for unittest
|
# init file required for unittest
|
||||||
custom_components/__init__.py
|
custom_components/__init__.py
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
|
config/**
|
||||||
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@@ -3,36 +3,15 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
// Example of attaching to local debug server
|
"name": "Home Assistant (debug)",
|
||||||
"name": "Python: Attach Local",
|
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "attach",
|
"request": "launch",
|
||||||
"port": 5678,
|
"module": "homeassistant",
|
||||||
"host": "localhost",
|
|
||||||
"justMyCode": false,
|
"justMyCode": false,
|
||||||
"pathMappings": [
|
"args": [
|
||||||
// {
|
"--debug",
|
||||||
// "localRoot": "${workspaceFolder}",
|
"-c",
|
||||||
// "remoteRoot": "."
|
"config"
|
||||||
//},
|
|
||||||
{
|
|
||||||
"localRoot": "${workspaceFolder}/../core",
|
|
||||||
"remoteRoot": "/home/vscode/core"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Example of attaching to my production server
|
|
||||||
"name": "Python: Attach Remote",
|
|
||||||
"type": "python",
|
|
||||||
"request": "attach",
|
|
||||||
"port": 5678,
|
|
||||||
"host": "homeassistant.local",
|
|
||||||
"pathMappings": [
|
|
||||||
{
|
|
||||||
"localRoot": "${workspaceFolder}",
|
|
||||||
"remoteRoot": "/usr/src/homeassistant"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@@ -1,12 +1,20 @@
|
|||||||
{
|
{
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||||
|
},
|
||||||
"python.linting.pylintEnabled": true,
|
"python.linting.pylintEnabled": true,
|
||||||
"python.linting.enabled": true,
|
"python.linting.enabled": true,
|
||||||
"python.pythonPath": "/usr/local/bin/python",
|
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.yaml": "home-assistant"
|
"*.yaml": "home-assistant"
|
||||||
},
|
},
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
"python.analysis.extraPaths": [
|
"python.analysis.extraPaths": [
|
||||||
"/home/vscode/core",
|
// "/home/vscode/core",
|
||||||
"/workspaces/versatile_thermostat"
|
"/workspaces/custom_components/versatile_thermostat"
|
||||||
]
|
],
|
||||||
|
"python.formatting.provider": "none"
|
||||||
}
|
}
|
||||||
61
CONTRIBUTING-fr.md
Normal file
61
CONTRIBUTING-fr.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Consignes de contribution
|
||||||
|
|
||||||
|
Contribuer à ce projet doit être aussi simple et transparent que possible, que ce soit :
|
||||||
|
|
||||||
|
- Signaler un bug
|
||||||
|
- Discuter de l'état actuel du code
|
||||||
|
- Soumettre un correctif
|
||||||
|
- Proposer de nouvelles fonctionnalités
|
||||||
|
|
||||||
|
## Github est utilisé pour tout
|
||||||
|
|
||||||
|
Github est utilisé pour héberger du code, pour suivre les problèmes et les demandes de fonctionnalités, ainsi que pour accepter les demandes d'extraction.
|
||||||
|
|
||||||
|
Les demandes d'extraction sont le meilleur moyen de proposer des modifications à la base de code.
|
||||||
|
|
||||||
|
1. Fourchez le dépôt et créez votre branche à partir de `master`.
|
||||||
|
2. Si vous avez modifié quelque chose, mettez à jour la documentation.
|
||||||
|
3. Assurez-vous que votre code peluche (en utilisant du noir).
|
||||||
|
4. Testez votre contribution.
|
||||||
|
5. Émettez cette pull request !
|
||||||
|
|
||||||
|
## Toutes les contributions que vous ferez seront sous la licence logicielle MIT
|
||||||
|
|
||||||
|
En bref, lorsque vous soumettez des modifications de code, vos soumissions sont considérées comme étant sous la même [licence MIT](http://choosealicense.com/licenses/mit/) qui couvre le projet. N'hésitez pas à contacter les mainteneurs si cela vous préoccupe.
|
||||||
|
|
||||||
|
## Signaler les bogues en utilisant les [issues] de Github (../../issues)
|
||||||
|
|
||||||
|
Les problèmes GitHub sont utilisés pour suivre les bogues publics.
|
||||||
|
Signalez un bogue en [ouvrant un nouveau problème](../../issues/new/choose) ; C'est si facile!
|
||||||
|
|
||||||
|
## Rédiger des rapports de bogue avec des détails, un arrière-plan et un exemple de code
|
||||||
|
|
||||||
|
Les **rapports de bogues géniaux** ont tendance à avoir :
|
||||||
|
|
||||||
|
- Un résumé rapide et/ou un historique
|
||||||
|
- Étapes à reproduire
|
||||||
|
- Être spécifique!
|
||||||
|
- Donnez un exemple de code si vous le pouvez.
|
||||||
|
- Ce à quoi vous vous attendiez arriverait
|
||||||
|
- Que se passe-t-il réellement
|
||||||
|
- Notes (y compris éventuellement pourquoi vous pensez que cela pourrait se produire, ou des choses que vous avez essayées qui n'ont pas fonctionné)
|
||||||
|
|
||||||
|
Les gens *adorent* les rapports de bogues approfondis. Je ne plaisante même pas.
|
||||||
|
|
||||||
|
## Utilisez un style de codage cohérent
|
||||||
|
|
||||||
|
Utilisez [black](https://github.com/ambv/black) pour vous assurer que le code suit le style.
|
||||||
|
|
||||||
|
## Testez votre modification de code
|
||||||
|
|
||||||
|
Ce composant personnalisé est basé sur les meilleures pratiques décrites ici [modèle d'intégration_blueprint](https://github.com/custom-components/integration_blueprint).
|
||||||
|
|
||||||
|
Il est livré avec un environnement de développement dans un conteneur, facile à lancer
|
||||||
|
si vous utilisez Visual Studio Code. Avec ce conteneur, vous aurez un stand alone
|
||||||
|
Instance de Home Assistant en cours d'exécution et déjà configurée avec le inclus
|
||||||
|
[`.devcontainer/configuration.yaml`](./.devcontainer/configuration.yaml)
|
||||||
|
déposer.
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
En contribuant, vous acceptez que vos contributions soient autorisées sous sa licence MIT.
|
||||||
61
CONTRIBUTING.md
Normal file
61
CONTRIBUTING.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Contribution guidelines
|
||||||
|
|
||||||
|
Contributing to this project should be as easy and transparent as possible, whether it's:
|
||||||
|
|
||||||
|
- Reporting a bug
|
||||||
|
- Discussing the current state of the code
|
||||||
|
- Submitting a fix
|
||||||
|
- Proposing new features
|
||||||
|
|
||||||
|
## Github is used for everything
|
||||||
|
|
||||||
|
Github is used to host code, to track issues and feature requests, as well as accept pull requests.
|
||||||
|
|
||||||
|
Pull requests are the best way to propose changes to the codebase.
|
||||||
|
|
||||||
|
1. Fork the repo and create your branch from `master`.
|
||||||
|
2. If you've changed something, update the documentation.
|
||||||
|
3. Make sure your code lints (using black).
|
||||||
|
4. Test you contribution.
|
||||||
|
5. Issue that pull request!
|
||||||
|
|
||||||
|
## Any contributions you make will be under the MIT Software License
|
||||||
|
|
||||||
|
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
|
||||||
|
|
||||||
|
## Report bugs using Github's [issues](../../issues)
|
||||||
|
|
||||||
|
GitHub issues are used to track public bugs.
|
||||||
|
Report a bug by [opening a new issue](../../issues/new/choose); it's that easy!
|
||||||
|
|
||||||
|
## Write bug reports with detail, background, and sample code
|
||||||
|
|
||||||
|
**Great Bug Reports** tend to have:
|
||||||
|
|
||||||
|
- A quick summary and/or background
|
||||||
|
- Steps to reproduce
|
||||||
|
- Be specific!
|
||||||
|
- Give sample code if you can.
|
||||||
|
- What you expected would happen
|
||||||
|
- What actually happens
|
||||||
|
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
||||||
|
|
||||||
|
People *love* thorough bug reports. I'm not even kidding.
|
||||||
|
|
||||||
|
## Use a Consistent Coding Style
|
||||||
|
|
||||||
|
Use [black](https://github.com/ambv/black) to make sure the code follows the style.
|
||||||
|
|
||||||
|
## Test your code modification
|
||||||
|
|
||||||
|
This custom component is based on best practices described here [integration_blueprint template](https://github.com/custom-components/integration_blueprint).
|
||||||
|
|
||||||
|
It comes with development environment in a container, easy to launch
|
||||||
|
if you use Visual Studio Code. With this container you will have a stand alone
|
||||||
|
Home Assistant instance running and already configured with the included
|
||||||
|
[`.devcontainer/configuration.yaml`](./.devcontainer/configuration.yaml)
|
||||||
|
file.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing, you agree that your contributions will be licensed under its MIT License.
|
||||||
18
container
18
container
@@ -4,15 +4,12 @@
|
|||||||
|
|
||||||
. .bashrc
|
. .bashrc
|
||||||
|
|
||||||
cd $HA
|
|
||||||
|
|
||||||
function get_dev() {
|
function get_dev() {
|
||||||
cd /workspaces/versatile_thermostat/custom_components/versatile_thermostat/
|
|
||||||
pip install pytest
|
|
||||||
pip install -r requirements_dev.txt
|
pip install -r requirements_dev.txt
|
||||||
pip install -r requirements_test.txt
|
pip install -r requirements_test.txt
|
||||||
sudo chown -R vscode: /home/vscode/core
|
if [ -d /home/vscode/core ]; then
|
||||||
cd -
|
sudo chown -R vscode: /home/vscode/core
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "arguments are: "$1
|
echo "arguments are: "$1
|
||||||
@@ -20,8 +17,7 @@ echo "arguments are: "$1
|
|||||||
case $1 in
|
case $1 in
|
||||||
start)
|
start)
|
||||||
echo "Running container start"
|
echo "Running container start"
|
||||||
cd $HA
|
./scripts/starts_ha.sh
|
||||||
hass -c ./config --debug
|
|
||||||
;;
|
;;
|
||||||
dev-setup)
|
dev-setup)
|
||||||
get_dev
|
get_dev
|
||||||
@@ -43,8 +39,8 @@ case $1 in
|
|||||||
restart)
|
restart)
|
||||||
echo "Killing existing container"
|
echo "Killing existing container"
|
||||||
pkill hass
|
pkill hass
|
||||||
echo "Killing existing container"
|
echo "Restarting existing container"
|
||||||
cd $HA
|
pwd
|
||||||
hass -c ./config
|
./scripts/starts_ha.sh
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ async def async_setup_entry(
|
|||||||
class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a BinarySensor which exposes the security state"""
|
"""Representation of a BinarySensor which exposes the security state"""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id, name, entry_infos
|
||||||
|
) -> None: # pylint: disable=unused-argument
|
||||||
"""Initialize the SecurityState Binary sensor"""
|
"""Initialize the SecurityState Binary sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||||
self._attr_name = "Security state"
|
self._attr_name = "Security state"
|
||||||
@@ -87,7 +89,9 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
|||||||
class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a BinarySensor which exposes the overpowering state"""
|
"""Representation of a BinarySensor which exposes the overpowering state"""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id, name, entry_infos
|
||||||
|
) -> None: # pylint: disable=unused-argument
|
||||||
"""Initialize the OverpoweringState Binary sensor"""
|
"""Initialize the OverpoweringState Binary sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||||
self._attr_name = "Overpowering state"
|
self._attr_name = "Overpowering state"
|
||||||
@@ -120,7 +124,9 @@ class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
|
|||||||
class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a BinarySensor which exposes the window state"""
|
"""Representation of a BinarySensor which exposes the window state"""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id, name, entry_infos
|
||||||
|
) -> None: # pylint: disable=unused-argument
|
||||||
"""Initialize the WindowState Binary sensor"""
|
"""Initialize the WindowState Binary sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||||
self._attr_name = "Window state"
|
self._attr_name = "Window state"
|
||||||
@@ -134,7 +140,10 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
old_state = self._attr_is_on
|
old_state = self._attr_is_on
|
||||||
# Issue 120 - only take defined presence value
|
# Issue 120 - only take defined presence value
|
||||||
if self.my_climate.window_state in [STATE_ON, STATE_OFF] or self.my_climate.window_auto_state in [STATE_ON, STATE_OFF]:
|
if self.my_climate.window_state in [
|
||||||
|
STATE_ON,
|
||||||
|
STATE_OFF,
|
||||||
|
] or self.my_climate.window_auto_state in [STATE_ON, STATE_OFF]:
|
||||||
self._attr_is_on = (
|
self._attr_is_on = (
|
||||||
self.my_climate.window_state == STATE_ON
|
self.my_climate.window_state == STATE_ON
|
||||||
or self.my_climate.window_auto_state == STATE_ON
|
or self.my_climate.window_auto_state == STATE_ON
|
||||||
@@ -161,7 +170,9 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
|||||||
class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a BinarySensor which exposes the motion state"""
|
"""Representation of a BinarySensor which exposes the motion state"""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id, name, entry_infos
|
||||||
|
) -> None: # pylint: disable=unused-argument
|
||||||
"""Initialize the MotionState Binary sensor"""
|
"""Initialize the MotionState Binary sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||||
self._attr_name = "Motion state"
|
self._attr_name = "Motion state"
|
||||||
@@ -195,7 +206,9 @@ class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
|||||||
class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a BinarySensor which exposes the presence state"""
|
"""Representation of a BinarySensor which exposes the presence state"""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id, name, entry_infos
|
||||||
|
) -> None: # pylint: disable=unused-argument
|
||||||
"""Initialize the PresenceState Binary sensor"""
|
"""Initialize the PresenceState Binary sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||||
self._attr_name = "Presence state"
|
self._attr_name = "Presence state"
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# pylint: disable=line-too-long
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
|
# pylint: disable=invalid-name
|
||||||
""" Implements the VersatileThermostat climate component """
|
""" Implements the VersatileThermostat climate component """
|
||||||
import math
|
import math
|
||||||
import logging
|
import logging
|
||||||
@@ -652,7 +655,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
# Initialize all UnderlyingEntities
|
# Initialize all UnderlyingEntities
|
||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
under.startup()
|
try:
|
||||||
|
under.startup()
|
||||||
|
except UnknownEntity:
|
||||||
|
# Not found, we will try later
|
||||||
|
pass
|
||||||
|
|
||||||
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
||||||
if temperature_state and temperature_state.state not in (
|
if temperature_state and temperature_state.state not in (
|
||||||
@@ -1312,7 +1319,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.recalculate()
|
self.recalculate()
|
||||||
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
||||||
|
|
||||||
def reset_last_change_time(self, old_preset_mode=None):
|
def reset_last_change_time(
|
||||||
|
self, old_preset_mode=None
|
||||||
|
): # pylint: disable=unused-argument
|
||||||
"""Reset to now the last change time"""
|
"""Reset to now the last change time"""
|
||||||
self._last_change_time = datetime.now(tz=self._current_tz)
|
self._last_change_time = datetime.now(tz=self._current_tz)
|
||||||
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
|
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
|
||||||
@@ -1546,7 +1555,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# Check delay condition
|
# Check delay condition
|
||||||
async def try_motion_condition(_):
|
async def try_motion_condition(_):
|
||||||
try:
|
try:
|
||||||
delay = self._motion_delay_sec if new_state.state == STATE_ON else self._motion_off_delay_sec
|
delay = (
|
||||||
|
self._motion_delay_sec
|
||||||
|
if new_state.state == STATE_ON
|
||||||
|
else self._motion_off_delay_sec
|
||||||
|
)
|
||||||
long_enough = condition.state(
|
long_enough = condition.state(
|
||||||
self.hass,
|
self.hass,
|
||||||
self._motion_sensor_entity_id,
|
self._motion_sensor_entity_id,
|
||||||
@@ -1583,13 +1596,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
self._motion_call_cancel = None
|
self._motion_call_cancel = None
|
||||||
|
|
||||||
im_on = (self._motion_state == STATE_ON)
|
im_on = self._motion_state == STATE_ON
|
||||||
delay_running = (self._motion_call_cancel is not None)
|
delay_running = self._motion_call_cancel is not None
|
||||||
event_on = (new_state.state == STATE_ON)
|
event_on = new_state.state == STATE_ON
|
||||||
|
|
||||||
def arm():
|
def arm():
|
||||||
""" Arm the timer"""
|
"""Arm the timer"""
|
||||||
delay = self._motion_delay_sec if new_state.state == STATE_ON else self._motion_off_delay_sec
|
delay = (
|
||||||
|
self._motion_delay_sec
|
||||||
|
if new_state.state == STATE_ON
|
||||||
|
else self._motion_off_delay_sec
|
||||||
|
)
|
||||||
self._motion_call_cancel = async_call_later(
|
self._motion_call_cancel = async_call_later(
|
||||||
self.hass, timedelta(seconds=delay), try_motion_condition
|
self.hass, timedelta(seconds=delay), try_motion_condition
|
||||||
)
|
)
|
||||||
@@ -1602,7 +1619,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# if I'm off
|
# if I'm off
|
||||||
if not im_on:
|
if not im_on:
|
||||||
if event_on and not delay_running:
|
if event_on and not delay_running:
|
||||||
_LOGGER.debug("%s - Arm delay cause i'm off and event is on and no delay is running", self)
|
_LOGGER.debug(
|
||||||
|
"%s - Arm delay cause i'm off and event is on and no delay is running",
|
||||||
|
self,
|
||||||
|
)
|
||||||
arm()
|
arm()
|
||||||
return try_motion_condition
|
return try_motion_condition
|
||||||
# Ignore the event
|
# Ignore the event
|
||||||
@@ -1614,7 +1634,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
arm()
|
arm()
|
||||||
return try_motion_condition
|
return try_motion_condition
|
||||||
if event_on and delay_running:
|
if event_on and delay_running:
|
||||||
_LOGGER.debug("%s - Desarm off delay cause i'm on and event is on and a delay is running", self)
|
_LOGGER.debug(
|
||||||
|
"%s - Desarm off delay cause i'm on and event is on and a delay is running",
|
||||||
|
self,
|
||||||
|
)
|
||||||
desarm()
|
desarm()
|
||||||
return None
|
return None
|
||||||
# Ignore the event
|
# Ignore the event
|
||||||
@@ -1696,9 +1719,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
|
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
|
||||||
# Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is
|
# Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is
|
||||||
# if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE:
|
# if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE:
|
||||||
# _LOGGER.debug(
|
# _LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF")
|
||||||
# "The underlying switch to idle instead of OFF. We will consider it as OFF"
|
|
||||||
# )
|
|
||||||
# new_hvac_mode = HVACMode.OFF
|
# new_hvac_mode = HVACMode.OFF
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -1710,17 +1731,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
old_hvac_action,
|
old_hvac_action,
|
||||||
)
|
)
|
||||||
|
|
||||||
if new_hvac_mode in [
|
_LOGGER.debug(
|
||||||
HVACMode.OFF,
|
"%s - last_change_time=%s old_state_date_changed=%s old_state_date_updated=%s new_state_date_changed=%s new_state_date_updated=%s",
|
||||||
HVACMode.HEAT,
|
self,
|
||||||
HVACMode.COOL,
|
self._last_change_time,
|
||||||
HVACMode.HEAT_COOL,
|
old_state_date_changed,
|
||||||
HVACMode.DRY,
|
old_state_date_updated,
|
||||||
HVACMode.AUTO,
|
new_state_date_changed,
|
||||||
HVACMode.FAN_ONLY,
|
new_state_date_updated,
|
||||||
None,
|
)
|
||||||
]:
|
|
||||||
self._hvac_mode = new_hvac_mode
|
|
||||||
|
|
||||||
# Interpretation of hvac action
|
# Interpretation of hvac action
|
||||||
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
|
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
-r requirements_dev.txt
|
-r requirements_dev.txt
|
||||||
aiodiscover
|
aiodiscover
|
||||||
ulid_transform
|
ulid_transform
|
||||||
|
pytest-asyncio
|
||||||
pytest-homeassistant-custom-component
|
pytest-homeassistant-custom-component
|
||||||
28
scripts/starts_ha.sh
Executable file
28
scripts/starts_ha.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
pwd
|
||||||
|
|
||||||
|
# Create config dir if not present
|
||||||
|
if [[ ! -d "${PWD}/config" ]]; then
|
||||||
|
mkdir -p "${PWD}/config"
|
||||||
|
# Add defaults configuration
|
||||||
|
hass --config "${PWD}/config" --script ensure_config
|
||||||
|
# Overwrite configuration.yaml if provided
|
||||||
|
if [ -f ${PWD}/.devcontainer/configuration.yaml ]; then
|
||||||
|
rm -f ${PWD}/config/configuration.yaml
|
||||||
|
ln -s ${PWD}/.devcontainer/configuration.yaml ${PWD}/config/configuration.yaml
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the path to custom_components
|
||||||
|
## This let's us have the structure we want <root>/custom_components/integration_blueprint
|
||||||
|
## while at the same time have Home Assistant configuration inside <root>/config
|
||||||
|
## without resulting to symlinks.
|
||||||
|
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"
|
||||||
|
|
||||||
|
# Start Home Assistant
|
||||||
|
hass --config "${PWD}/config" --debug
|
||||||
3
setup.cfg
Normal file
3
setup.cfg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tool:pytest]
|
||||||
|
testpaths = tests
|
||||||
|
asyncio_mode = auto
|
||||||
@@ -20,9 +20,9 @@ from homeassistant.components.climate import (
|
|||||||
|
|
||||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||||
|
|
||||||
from ..climate import VersatileThermostat
|
from custom_components.versatile_thermostat.climate import VersatileThermostat
|
||||||
from ..const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from custom_components.versatile_thermostat.const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
from ..underlyings import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from custom_components.versatile_thermostat.underlyings import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
from .const import ( # pylint: disable=unused-import
|
from .const import ( # pylint: disable=unused-import
|
||||||
MOCK_TH_OVER_SWITCH_USER_CONFIG,
|
MOCK_TH_OVER_SWITCH_USER_CONFIG,
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Global fixtures for integration_blueprint integration."""
|
"""Global fixtures for integration_blueprint integration."""
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
# Fixtures allow you to replace functions with a Mock object. You can perform
|
# Fixtures allow you to replace functions with a Mock object. You can perform
|
||||||
# many options via the Mock to reflect a particular behavior from the original
|
# many options via the Mock to reflect a particular behavior from the original
|
||||||
# function that you want to see without going through the function's actual logic.
|
# function that you want to see without going through the function's actual logic.
|
||||||
@@ -34,7 +36,7 @@ pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=inva
|
|||||||
# This fixture enables loading custom integrations in all tests.
|
# This fixture enables loading custom integrations in all tests.
|
||||||
# Remove to enable selective use of this fixture
|
# Remove to enable selective use of this fixture
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def auto_enable_custom_integrations(enable_custom_integrations):
|
def auto_enable_custom_integrations(enable_custom_integrations): # pylint: disable=unused-argument
|
||||||
"""Enable all integration in tests"""
|
"""Enable all integration in tests"""
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@@ -9,9 +9,8 @@ from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
|||||||
|
|
||||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||||
|
|
||||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from custom_components.versatile_thermostat.climate import VersatileThermostat
|
||||||
from ..climate import VersatileThermostat
|
from custom_components.versatile_thermostat.binary_sensor import (
|
||||||
from ..binary_sensor import (
|
|
||||||
SecurityBinarySensor,
|
SecurityBinarySensor,
|
||||||
OverpoweringBinarySensor,
|
OverpoweringBinarySensor,
|
||||||
WindowBinarySensor,
|
WindowBinarySensor,
|
||||||
@@ -29,7 +28,7 @@ async def test_security_binary_sensors(
|
|||||||
skip_hass_states_is_state,
|
skip_hass_states_is_state,
|
||||||
skip_turn_on_off_heater,
|
skip_turn_on_off_heater,
|
||||||
skip_send_event,
|
skip_send_event,
|
||||||
):
|
): # pylint: disable=unused-argument
|
||||||
"""Test the security binary sensors in thermostat type"""
|
"""Test the security binary sensors in thermostat type"""
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
@@ -29,7 +29,7 @@ async def test_show_form(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_user_config_flow_over_switch(hass: HomeAssistant, skip_hass_states_get):
|
async def test_user_config_flow_over_switch(hass: HomeAssistant, skip_hass_states_get): # pylint: disable=unused-argument
|
||||||
"""Test the config flow with all thermostat_over_switch features"""
|
"""Test the config flow with all thermostat_over_switch features"""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
@@ -128,7 +128,7 @@ async def test_user_config_flow_over_switch(hass: HomeAssistant, skip_hass_state
|
|||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_user_config_flow_over_climate(hass: HomeAssistant, skip_hass_states_get):
|
async def test_user_config_flow_over_climate(hass: HomeAssistant, skip_hass_states_get): # pylint: disable=unused-argument
|
||||||
"""Test the config flow with all thermostat_over_climate features and no additional features"""
|
"""Test the config flow with all thermostat_over_climate features and no additional features"""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
@@ -184,7 +184,7 @@ async def test_user_config_flow_over_climate(hass: HomeAssistant, skip_hass_stat
|
|||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_user_config_flow_window_auto_ok(
|
async def test_user_config_flow_window_auto_ok(
|
||||||
hass: HomeAssistant, skip_hass_states_get, skip_control_heating
|
hass: HomeAssistant, skip_hass_states_get, skip_control_heating # pylint: disable=unused-argument
|
||||||
):
|
):
|
||||||
"""Test the config flow with only window auto feature"""
|
"""Test the config flow with only window auto feature"""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@@ -281,7 +281,7 @@ async def test_user_config_flow_window_auto_ok(
|
|||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_user_config_flow_window_auto_ko(
|
async def test_user_config_flow_window_auto_ko(
|
||||||
hass: HomeAssistant, skip_hass_states_get
|
hass: HomeAssistant, skip_hass_states_get # pylint: disable=unused-argument
|
||||||
):
|
):
|
||||||
"""Test the config flow with window auto and window features -> not allowed"""
|
"""Test the config flow with window auto and window features -> not allowed"""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@@ -353,7 +353,7 @@ async def test_user_config_flow_window_auto_ko(
|
|||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_user_config_flow_over_4_switches(
|
async def test_user_config_flow_over_4_switches(
|
||||||
hass: HomeAssistant, skip_hass_states_get, skip_control_heating
|
hass: HomeAssistant, skip_hass_states_get, skip_control_heating # pylint: disable=unused-argument
|
||||||
):
|
):
|
||||||
"""Test the config flow with 4 switchs thermostat_over_switch features"""
|
"""Test the config flow with 4 switchs thermostat_over_switch features"""
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
""" Test the Multiple switch management """
|
""" Test the Multiple switch management """
|
||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import patch, call, ANY
|
from unittest.mock import patch, call, ANY
|
||||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ async def test_one_switch_cycle(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
skip_hass_states_is_state,
|
skip_hass_states_is_state,
|
||||||
skip_send_event,
|
skip_send_event,
|
||||||
):
|
): # pylint: disable=unused-argument
|
||||||
"""Test that when multiple switch are configured the activation is distributed"""
|
"""Test that when multiple switch are configured the activation is distributed"""
|
||||||
|
|
||||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
@@ -75,7 +75,7 @@ async def test_one_switch_cycle(
|
|||||||
with patch(
|
with patch(
|
||||||
"homeassistant.core.StateMachine.is_state", return_value=False
|
"homeassistant.core.StateMachine.is_state", return_value=False
|
||||||
) as mock_is_state:
|
) as mock_is_state:
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
# Should be call for the Switch
|
# Should be call for the Switch
|
||||||
assert mock_is_state.call_count == 1
|
assert mock_is_state.call_count == 1
|
||||||
@@ -132,7 +132,8 @@ async def test_one_switch_cycle(
|
|||||||
assert mock_send_event.call_count == 0
|
assert mock_send_event.call_count == 0
|
||||||
assert mock_heater_off.call_count == 0
|
assert mock_heater_off.call_count == 0
|
||||||
|
|
||||||
# The first heater should be turned on but is already on but because above we mock call_later the heater is not on. But this time it will be really on
|
# The first heater should be turned on but is already on but because above we mock
|
||||||
|
# call_later the heater is not on. But this time it will be really on
|
||||||
assert mock_heater_on.call_count == 1
|
assert mock_heater_on.call_count == 1
|
||||||
|
|
||||||
# Set another temperature at middle level
|
# Set another temperature at middle level
|
||||||
@@ -153,12 +154,15 @@ async def test_one_switch_cycle(
|
|||||||
assert mock_send_event.call_count == 0
|
assert mock_send_event.call_count == 0
|
||||||
assert mock_heater_off.call_count == 0
|
assert mock_heater_off.call_count == 0
|
||||||
|
|
||||||
# The heater is already on cycle. So we wait that the cycle ends and no heater action is done
|
# The heater is already on cycle. So we wait that the cycle ends and no heater action
|
||||||
|
# is done
|
||||||
assert mock_heater_on.call_count == 0
|
assert mock_heater_on.call_count == 0
|
||||||
# assert entity.underlying_entity(0)._should_relaunch_control_heating is True
|
# assert entity.underlying_entity(0)._should_relaunch_control_heating is True
|
||||||
|
|
||||||
# Simulate the relaunch
|
# Simulate the relaunch
|
||||||
await entity.underlying_entity(0)._turn_on_later(None)
|
await entity.underlying_entity(0)._turn_on_later( # pylint: disable=protected-access
|
||||||
|
None
|
||||||
|
)
|
||||||
# wait restart
|
# wait restart
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
@@ -177,7 +181,9 @@ async def test_one_switch_cycle(
|
|||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await entity.underlying_entity(0)._turn_off_later(None)
|
await entity.underlying_entity(0)._turn_off_later( # pylint: disable=protected-access
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
# No special event
|
# No special event
|
||||||
assert mock_send_event.call_count == 0
|
assert mock_send_event.call_count == 0
|
||||||
@@ -198,7 +204,9 @@ async def test_one_switch_cycle(
|
|||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await entity.underlying_entity(0)._turn_on_later(None)
|
await entity.underlying_entity(0)._turn_on_later( # pylint: disable=protected-access
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
# No special event
|
# No special event
|
||||||
assert mock_send_event.call_count == 0
|
assert mock_send_event.call_count == 0
|
||||||
@@ -214,7 +222,7 @@ async def test_multiple_switchs(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
skip_hass_states_is_state,
|
skip_hass_states_is_state,
|
||||||
skip_send_event,
|
skip_send_event,
|
||||||
):
|
): # pylint: disable=unused-argument
|
||||||
"""Test that when multiple switch are configured the activation is distributed"""
|
"""Test that when multiple switch are configured the activation is distributed"""
|
||||||
|
|
||||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
@@ -277,7 +285,7 @@ async def test_multiple_switchs(
|
|||||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||||
|
|
||||||
# Checks that all climates are off
|
# Checks that all climates are off
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
# Should be call for all Switch
|
# Should be call for all Switch
|
||||||
assert mock_underlying_set_hvac_mode.call_count == 4
|
assert mock_underlying_set_hvac_mode.call_count == 4
|
||||||
@@ -342,17 +350,20 @@ async def test_multiple_switchs(
|
|||||||
assert mock_send_event.call_count == 0
|
assert mock_send_event.call_count == 0
|
||||||
assert mock_heater_off.call_count == 0
|
assert mock_heater_off.call_count == 0
|
||||||
|
|
||||||
# The first heater should be turned on but is already on but because call_later is mocked, it is only turned on here
|
# The first heater should be turned on but is already on but because call_later
|
||||||
|
# is mocked, it is only turned on here
|
||||||
assert mock_heater_on.call_count == 1
|
assert mock_heater_on.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_multiple_climates(
|
async def test_multiple_climates(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
skip_hass_states_is_state,
|
skip_hass_states_is_state,
|
||||||
skip_send_event,
|
skip_send_event,
|
||||||
):
|
): # pylint: disable=unused-argument
|
||||||
"""Test that when multiple climates are configured the activation and deactivation is propagated to all climates"""
|
"""Test that when multiple climates are configured the activation and deactivation
|
||||||
|
is propagated to all climates"""
|
||||||
|
|
||||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
now: datetime = datetime.now(tz=tz)
|
now: datetime = datetime.now(tz=tz)
|
||||||
@@ -416,7 +427,7 @@ async def test_multiple_climates(
|
|||||||
call.set_hvac_mode(HVACMode.HEAT),
|
call.set_hvac_mode(HVACMode.HEAT),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
# Stop heating, in boost mode. We block the control_heating to avoid running a cycle
|
# Stop heating, in boost mode. We block the control_heating to avoid running a cycle
|
||||||
with patch(
|
with patch(
|
||||||
@@ -441,7 +452,8 @@ async def test_multiple_climates(
|
|||||||
call.set_hvac_mode(HVACMode.OFF),
|
call.set_hvac_mode(HVACMode.OFF),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
@@ -449,8 +461,9 @@ async def test_multiple_climates_underlying_changes(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
skip_hass_states_is_state,
|
skip_hass_states_is_state,
|
||||||
skip_send_event,
|
skip_send_event,
|
||||||
):
|
): # pylint: disable=unused-argument
|
||||||
"""Test that when multiple switch are configured the activation of one underlying climate activate the others"""
|
"""Test that when multiple switch are configured the activation of one underlying
|
||||||
|
climate activate the others"""
|
||||||
|
|
||||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
now: datetime = datetime.now(tz=tz)
|
now: datetime = datetime.now(tz=tz)
|
||||||
@@ -514,7 +527,7 @@ async def test_multiple_climates_underlying_changes(
|
|||||||
call.set_hvac_mode(HVACMode.HEAT),
|
call.set_hvac_mode(HVACMode.HEAT),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
# Stop heating on one underlying climate
|
# Stop heating on one underlying climate
|
||||||
with patch(
|
with patch(
|
||||||
@@ -524,7 +537,14 @@ async def test_multiple_climates_underlying_changes(
|
|||||||
) as mock_underlying_set_hvac_mode:
|
) as mock_underlying_set_hvac_mode:
|
||||||
# Wait 11 sec so that the event will not be discarded
|
# Wait 11 sec so that the event will not be discarded
|
||||||
event_timestamp = now + timedelta(seconds=11)
|
event_timestamp = now + timedelta(seconds=11)
|
||||||
await send_climate_change_event(entity, HVACMode.OFF, HVACMode.HEAT, HVACAction.OFF, HVACAction.HEATING, event_timestamp)
|
await send_climate_change_event(
|
||||||
|
entity,
|
||||||
|
HVACMode.OFF,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACAction.OFF,
|
||||||
|
HVACAction.HEATING,
|
||||||
|
event_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
# Should be call for all Switch
|
# Should be call for all Switch
|
||||||
assert mock_underlying_set_hvac_mode.call_count == 4
|
assert mock_underlying_set_hvac_mode.call_count == 4
|
||||||
@@ -534,7 +554,7 @@ async def test_multiple_climates_underlying_changes(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert entity.hvac_mode == HVACMode.OFF
|
assert entity.hvac_mode == HVACMode.OFF
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
# Start heating on one underlying climate
|
# Start heating on one underlying climate
|
||||||
with patch(
|
with patch(
|
||||||
@@ -542,12 +562,21 @@ async def test_multiple_climates_underlying_changes(
|
|||||||
), patch(
|
), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||||
) as mock_underlying_set_hvac_mode, patch(
|
) as mock_underlying_set_hvac_mode, patch(
|
||||||
# notice that there is no need of return_value=HVACAction.IDLE because this is not a function but a property
|
# notice that there is no need of return_value=HVACAction.IDLE because this is not
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_action", HVACAction.IDLE
|
# a function but a property
|
||||||
) as mock_underlying_get_hvac_action:
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_action",
|
||||||
|
HVACAction.IDLE,
|
||||||
|
):
|
||||||
# Wait 11 sec so that the event will not be discarded
|
# Wait 11 sec so that the event will not be discarded
|
||||||
event_timestamp = now + timedelta(seconds=11)
|
event_timestamp = now + timedelta(seconds=11)
|
||||||
await send_climate_change_event(entity, HVACMode.HEAT, HVACMode.OFF, HVACAction.IDLE, HVACAction.OFF, event_timestamp)
|
await send_climate_change_event(
|
||||||
|
entity,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACMode.OFF,
|
||||||
|
HVACAction.IDLE,
|
||||||
|
HVACAction.OFF,
|
||||||
|
event_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
# Should be call for all Switch
|
# Should be call for all Switch
|
||||||
assert mock_underlying_set_hvac_mode.call_count == 4
|
assert mock_underlying_set_hvac_mode.call_count == 4
|
||||||
@@ -558,5 +587,4 @@ async def test_multiple_climates_underlying_changes(
|
|||||||
)
|
)
|
||||||
assert entity.hvac_mode == HVACMode.HEAT
|
assert entity.hvac_mode == HVACMode.HEAT
|
||||||
assert entity.hvac_action == HVACAction.IDLE
|
assert entity.hvac_action == HVACAction.IDLE
|
||||||
assert entity._is_device_active is False
|
assert entity._is_device_active is False # pylint: disable=protected-access
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
from ..open_window_algorithm import WindowOpenDetectionAlgorithm
|
from custom_components.versatile_thermostat.open_window_algorithm import WindowOpenDetectionAlgorithm
|
||||||
|
|
||||||
|
|
||||||
async def test_open_window_algo(
|
async def test_open_window_algo(
|
||||||
@@ -12,8 +12,8 @@ from homeassistant.const import UnitOfTime, UnitOfPower, UnitOfEnergy, PERCENTAG
|
|||||||
|
|
||||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||||
|
|
||||||
from ..climate import VersatileThermostat
|
from custom_components.versatile_thermostat.climate import VersatileThermostat
|
||||||
from ..sensor import (
|
from custom_components.versatile_thermostat.sensor import (
|
||||||
EnergySensor,
|
EnergySensor,
|
||||||
MeanPowerSensor,
|
MeanPowerSensor,
|
||||||
OnPercentSensor,
|
OnPercentSensor,
|
||||||
@@ -10,7 +10,7 @@ from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DO
|
|||||||
|
|
||||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||||
|
|
||||||
from ..climate import VersatileThermostat
|
from custom_components.versatile_thermostat.climate import VersatileThermostat
|
||||||
|
|
||||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
@@ -5,7 +5,9 @@ from .commons import * # pylint: disable=wildcard-import, unused-wildcard-impor
|
|||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state: None):
|
async def test_tpi_calculation(
|
||||||
|
hass: HomeAssistant, skip_hass_states_is_state: None
|
||||||
|
): # pylint: disable=unused-argument
|
||||||
"""Test the TPI calculation"""
|
"""Test the TPI calculation"""
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
@@ -40,7 +42,7 @@ async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state: N
|
|||||||
)
|
)
|
||||||
assert entity
|
assert entity
|
||||||
|
|
||||||
tpi_algo = entity._prop_algorithm
|
tpi_algo = entity._prop_algorithm # pylint: disable=protected-access
|
||||||
assert tpi_algo
|
assert tpi_algo
|
||||||
|
|
||||||
tpi_algo.calculate(15, 10, 7)
|
tpi_algo.calculate(15, 10, 7)
|
||||||
Reference in New Issue
Block a user