├── .github └── workflows │ ├── codeql.yml │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── ariston ├── __init__.py ├── ariston_api.py ├── base_device.py ├── bsb_device.py ├── const.py ├── device.py ├── evo_device.py ├── evo_lydos_device.py ├── evo_one_device.py ├── galevo_device.py ├── lux2_device.py ├── lux_device.py ├── lydos_device.py ├── lydos_hybrid_device.py ├── nuos_split_device.py ├── velis_base_device.py └── velis_device.py └── pyproject.toml /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '43 6 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 fustom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CodeQL](https://github.com/fustom/python-ariston-api/actions/workflows/codeql.yml/badge.svg)](https://github.com/fustom/python-ariston-api/actions/workflows/codeql.yml) [![Upload Python Package](https://github.com/fustom/python-ariston-api/actions/workflows/python-publish.yml/badge.svg)](https://github.com/fustom/python-ariston-api/actions/workflows/python-publish.yml) 2 | 3 | # python-ariston-api 4 | A Python module for controlling Ariston devices with cloud polling. 5 | The following devices are currently supported: 6 | - Ariston Alteas One 24 7 | - Ariston Velis Evo 8 | - Ariston Velis Lux 9 | - Ariston Lydos Hybrid 10 | - Ariston Genus One 11 | - Ariston Nuos Split 12 | - Ariston Thision S 13 | - Chaffoteaux INOA S 24 14 | 15 | ## Installation 16 | Use pip3 to install the latest version of this module. 17 | ``` 18 | pip3 install ariston 19 | ``` 20 | 21 | ## The easy way (recommended for testing the module) 22 | First, open Python 3 and import ariston module. 23 | ``` 24 | python3 25 | ``` 26 | ```python3 27 | import ariston 28 | ``` 29 | ### Syncronous 30 | Discover devices if you dont know your gateway id. You can skip this step. 31 | ```python3 32 | raw_devices = ariston.discover("username", "password") 33 | ``` 34 | For example the gateway id for your first device. 35 | ```python3 36 | raw_devices[0]['gw'] 37 | ``` 38 | Get your device 39 | ```python3 40 | device = ariston.hello("username", "password", "gateway", is_metric, "location") 41 | ``` 42 | [Go use your device section](#use-your-device) 43 | ### Asyncronous 44 | ```python3 45 | raw_devices = await ariston.async_discover("username", "password") 46 | device = await ariston.async_hello("username", "password", "gateway", is_metric, "location") 47 | ``` 48 | [Go use your device section](#use-your-device) 49 | ## The ariston class way (recommended for integrate the module) 50 | First, open Python 3 and import Ariston class from this module. 51 | ``` 52 | python3 53 | ``` 54 | ```python3 55 | from ariston import Ariston 56 | ``` 57 | Create a new Ariston instance 58 | ```python3 59 | ariston = Ariston() 60 | ``` 61 | Now let's try some functions 62 | 63 | ### Connect 64 | The cloud requests are asynchronous, so if you call them from a synchronous function or not even from function, you should use asyncio. 65 | ```python3 66 | import asyncio 67 | ``` 68 | 69 | Sync 70 | ```python3 71 | asyncio.run(ariston.async_connect("username", "password")) 72 | ``` 73 | Async 74 | ```python3 75 | await ariston.async_connect("username", "password") 76 | ``` 77 | - username: Your ariston cloud username. 78 | - password: Your ariston cloud password. 79 | 80 | ### Discovery 81 | Use this function to discover devices. You can skip this step if you already know the gateway id. 82 | 83 | Sync 84 | ```python3 85 | devices = asyncio.run(ariston.async_discover()) 86 | ``` 87 | Async 88 | ```python3 89 | devices = await ariston.async_discover() 90 | ``` 91 | 92 | ## Say hello 93 | Use this function to create the device object. 94 | 95 | Sync 96 | ```python3 97 | device = asyncio.run(ariston.async_hello("gateway", is_metric, "location")) 98 | ``` 99 | Async 100 | ```python3 101 | device = await ariston.async_hello("gateway", is_metric, "location") 102 | ``` 103 | - gateway: You can find the value in the returned discover dictionary name 'gw' 104 | - is_metric: Optional. True or False. True means metric, False means imperial. Only works with Galevo (Alteas One, Genus One, etc) system. Default is True. 105 | - language_tag: Optional. Check https://en.wikipedia.org/wiki/IETF_language_tag Only works with Galevo (Alteas One, Genus One, etc) system. Default is "en-US". 106 | 107 | ## Use your device 108 | ### Get device features 109 | Sync 110 | ```python3 111 | device.get_features() 112 | ``` 113 | Async 114 | ```python3 115 | await device.async_get_features() 116 | ``` 117 | ### Get device data 118 | Sync 119 | ```python3 120 | device.update_state() 121 | ``` 122 | Async 123 | ```python3 124 | await device.async_update_state() 125 | ``` 126 | ### Get device energy 127 | Sync 128 | ```python3 129 | device.update_energy() 130 | ``` 131 | Async 132 | ```python3 133 | await device.async_update_energy() 134 | ``` 135 | -------------------------------------------------------------------------------- /ariston/__init__.py: -------------------------------------------------------------------------------- 1 | """Ariston module""" 2 | 3 | import asyncio 4 | import logging 5 | from typing import Any, Optional, Type 6 | 7 | from .ariston_api import AristonAPI, ConnectionException 8 | from .const import ( 9 | ARISTON_API_URL, 10 | ARISTON_USER_AGENT, 11 | DeviceAttribute, 12 | SystemType, 13 | VelisDeviceAttribute, 14 | WheType, 15 | ) 16 | from .bsb_device import AristonBsbDevice 17 | from .lux_device import AristonLuxDevice 18 | from .lux2_device import AristonLux2Device 19 | from .evo_one_device import AristonEvoOneDevice 20 | from .evo_device import AristonEvoDevice 21 | from .galevo_device import AristonGalevoDevice 22 | from .lydos_hybrid_device import AristonLydosHybridDevice 23 | from .nuos_split_device import AristonNuosSplitDevice 24 | from .base_device import AristonBaseDevice 25 | from .velis_base_device import AristonVelisBaseDevice 26 | from .lydos_device import AristonLydosDevice 27 | 28 | _LOGGER = logging.getLogger(__name__) 29 | 30 | _MAP_WHE_TYPES_TO_CLASS: dict[int, Type[AristonVelisBaseDevice]] = { 31 | WheType.Evo.value: AristonEvoOneDevice, 32 | WheType.LydosHybrid.value: AristonLydosHybridDevice, 33 | WheType.Lydos.value: AristonLydosDevice, 34 | WheType.NuosSplit.value: AristonNuosSplitDevice, 35 | WheType.Andris2.value: AristonEvoDevice, 36 | WheType.Evo2.value: AristonEvoDevice, 37 | WheType.Lux2.value: AristonLux2Device, 38 | WheType.Lux.value: AristonLuxDevice, 39 | } 40 | 41 | 42 | class Ariston: 43 | """Ariston class""" 44 | 45 | def __init__(self) -> None: 46 | self.api = None 47 | self.cloud_devices: list[dict[str, Any]] = [] 48 | 49 | async def async_connect( 50 | self, 51 | username: str, 52 | password: str, 53 | api_url: str = ARISTON_API_URL, 54 | user_agent: str = ARISTON_USER_AGENT, 55 | ) -> bool: 56 | """Connect to the ariston cloud""" 57 | self.api = AristonAPI(username, password, api_url, user_agent) 58 | return await self.api.async_connect() 59 | 60 | async def async_discover(self) -> list[dict[str, Any]]: 61 | """Retreive ariston devices from the cloud""" 62 | if self.api is None: 63 | _LOGGER.exception("Call async_connect first") 64 | return [] 65 | cloud_devices = await _async_discover(self.api) 66 | self.cloud_devices = cloud_devices 67 | return cloud_devices 68 | 69 | async def async_hello( 70 | self, gateway: str, is_metric: bool = True, language_tag: str = "en-US" 71 | ) -> Optional[AristonBaseDevice]: 72 | """Get ariston device""" 73 | if self.api is None: 74 | _LOGGER.exception("Call async_connect() first") 75 | return None 76 | 77 | if len(self.cloud_devices) == 0: 78 | await self.async_discover() 79 | 80 | return _get_device( 81 | self.cloud_devices, self.api, gateway, is_metric, language_tag 82 | ) 83 | 84 | 85 | def _get_device( 86 | cloud_devices: list[dict[str, Any]], 87 | api: AristonAPI, 88 | gateway: str, 89 | is_metric: bool = True, 90 | language_tag: str = "en-US", 91 | ) -> Optional[AristonBaseDevice]: 92 | """Get ariston device""" 93 | device = next( 94 | (dev for dev in cloud_devices if dev.get(DeviceAttribute.GW) == gateway), 95 | None, 96 | ) 97 | if device is None: 98 | _LOGGER.exception("No device %s found.", gateway) 99 | return None 100 | 101 | system_type = device.get(DeviceAttribute.SYS) 102 | 103 | match system_type: 104 | case SystemType.GALEVO.value: 105 | return AristonGalevoDevice( 106 | api, 107 | device, 108 | is_metric, 109 | language_tag, 110 | ) 111 | case SystemType.VELIS.value: 112 | whe_type = device.get(VelisDeviceAttribute.WHE_TYPE, None) 113 | device_class = _MAP_WHE_TYPES_TO_CLASS.get(whe_type, None) 114 | if device_class is None: 115 | _LOGGER.exception("Unsupported whe type %s", whe_type) 116 | return None 117 | return device_class(api, device) 118 | 119 | case SystemType.BSB.value: 120 | return AristonBsbDevice(api, device) 121 | case _: 122 | _LOGGER.exception("Unsupported system type %s", system_type) 123 | return None 124 | 125 | 126 | def _connect( 127 | username: str, 128 | password: str, 129 | api_url: str = ARISTON_API_URL, 130 | user_agent: str = ARISTON_USER_AGENT, 131 | ) -> AristonAPI: 132 | """Connect to ariston api""" 133 | api = AristonAPI(username, password, api_url, user_agent) 134 | api.connect() 135 | return api 136 | 137 | 138 | def _discover(api: AristonAPI) -> list[dict[str, Any]]: 139 | """Retreive ariston devices from the cloud""" 140 | cloud_devices: list[dict[str, Any]] = [] 141 | cloud_devices.extend(api.get_detailed_devices()) 142 | cloud_devices.extend(api.get_detailed_velis_devices()) 143 | 144 | return cloud_devices 145 | 146 | 147 | def discover( 148 | username: str, password: str, api_url: str = ARISTON_API_URL 149 | ) -> list[dict[str, Any]]: 150 | """Retreive ariston devices from the cloud""" 151 | api = _connect(username, password, api_url) 152 | return _discover(api) 153 | 154 | 155 | def hello( 156 | username: str, 157 | password: str, 158 | gateway: str, 159 | is_metric: bool = True, 160 | language_tag: str = "en-US", 161 | api_url: str = ARISTON_API_URL, 162 | ) -> Optional[AristonBaseDevice]: 163 | """Get ariston device""" 164 | api = _connect(username, password, api_url) 165 | cloud_devices = _discover(api) 166 | return _get_device(cloud_devices, api, gateway, is_metric, language_tag) 167 | 168 | 169 | async def _async_connect( 170 | username: str, 171 | password: str, 172 | api_url: str = ARISTON_API_URL, 173 | user_agent: str = ARISTON_USER_AGENT, 174 | ) -> AristonAPI: 175 | """Async connect to ariston api""" 176 | api = AristonAPI(username, password, api_url, user_agent) 177 | if not await api.async_connect(): 178 | raise ConnectionException 179 | return api 180 | 181 | 182 | async def _async_discover(api: AristonAPI) -> list[dict[str, Any]]: 183 | """Async retreive ariston devices from the cloud""" 184 | cloud_devices: list[dict[str, Any]] = [] 185 | cloud_devices_tuple: tuple[ 186 | list[dict[str, Any]], list[dict[str, Any]] 187 | ] = await asyncio.gather( 188 | api.async_get_detailed_devices(), api.async_get_detailed_velis_devices() 189 | ) 190 | 191 | for devices in cloud_devices_tuple: 192 | cloud_devices.extend(devices) 193 | 194 | return cloud_devices 195 | 196 | 197 | async def async_discover( 198 | username: str, password: str, api_url: str = ARISTON_API_URL 199 | ) -> list[dict[str, Any]]: 200 | """Retreive ariston devices from the cloud""" 201 | api = await _async_connect(username, password, api_url) 202 | return await _async_discover(api) 203 | 204 | 205 | async def async_hello( 206 | username: str, 207 | password: str, 208 | gateway: str, 209 | is_metric: bool = True, 210 | language_tag: str = "en-US", 211 | api_url: str = ARISTON_API_URL, 212 | ) -> Optional[AristonBaseDevice]: 213 | """Get ariston device""" 214 | api = await _async_connect(username, password, api_url) 215 | cloud_devices = await _async_discover(api) 216 | return _get_device(cloud_devices, api, gateway, is_metric, language_tag) 217 | -------------------------------------------------------------------------------- /ariston/ariston_api.py: -------------------------------------------------------------------------------- 1 | """Ariston API""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | import time 7 | from typing import Any, Optional 8 | 9 | import asyncio 10 | import aiohttp 11 | import requests 12 | 13 | from .const import ( 14 | ARISTON_API_URL, 15 | ARISTON_USER_AGENT, 16 | ARISTON_BSB_ZONES, 17 | ARISTON_BUS_ERRORS, 18 | ARISTON_DATA_ITEMS, 19 | ARISTON_LITE, 20 | ARISTON_LOGIN, 21 | ARISTON_PLANTS, 22 | ARISTON_REMOTE, 23 | ARISTON_REPORTS, 24 | ARISTON_TIME_PROGS, 25 | ARISTON_VELIS, 26 | ARISTON_MENU_ITEMS, 27 | BsbOperativeMode, 28 | BsbZoneMode, 29 | DeviceFeatures, 30 | DeviceProperties, 31 | LydosPlantMode, 32 | NuosSplitOperativeMode, 33 | PlantData, 34 | ThermostatProperties, 35 | WaterHeaterMode, 36 | ZoneAttribute, 37 | MenuItemNames, 38 | ) 39 | 40 | _LOGGER = logging.getLogger(__name__) 41 | 42 | 43 | class ConnectionException(Exception): 44 | """When can not connect to Ariston cloud""" 45 | 46 | 47 | class AristonAPI: 48 | """Ariston API class""" 49 | 50 | def __init__( 51 | self, 52 | username: str, 53 | password: str, 54 | api_url: str = ARISTON_API_URL, 55 | user_agent: str = ARISTON_USER_AGENT, 56 | ) -> None: 57 | """Constructor for Ariston API.""" 58 | self.__username = username 59 | self.__password = password 60 | self.__api_url = api_url 61 | self.__token = "" 62 | self.__user_agent = user_agent 63 | 64 | def connect(self) -> bool: 65 | """Login to ariston cloud and get token""" 66 | 67 | try: 68 | response = self._post( 69 | f"{self.__api_url}{ARISTON_LOGIN}", 70 | {"usr": self.__username, "pwd": self.__password}, 71 | ) 72 | 73 | if response is None: 74 | return False 75 | 76 | self.__token = response["token"] 77 | 78 | return True 79 | 80 | except Exception as error: 81 | raise ConnectionException() from error 82 | 83 | def get_detailed_devices(self) -> list[Any]: 84 | """Get detailed cloud devices""" 85 | devices = self._get(f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}") 86 | if devices is not None: 87 | return list(devices) 88 | return list() 89 | 90 | def get_detailed_velis_devices(self) -> list[Any]: 91 | """Get detailed cloud devices""" 92 | devices = self._get(f"{self.__api_url}{ARISTON_VELIS}/{ARISTON_PLANTS}") 93 | if devices is not None: 94 | return list(devices) 95 | return list() 96 | 97 | def get_devices(self) -> list[Any]: 98 | """Get cloud devices""" 99 | devices = self._get( 100 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{ARISTON_LITE}" 101 | ) 102 | if devices is not None: 103 | return list(devices) 104 | return list() 105 | 106 | def get_features_for_device(self, gw_id: str) -> dict[str, Any]: 107 | """Get features for the device""" 108 | features = self._get( 109 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{gw_id}/features" 110 | ) 111 | if features is not None: 112 | return features 113 | return dict() 114 | 115 | def get_energy_account(self, gw_id: str) -> dict[str, Any]: 116 | """Get energy account for the device""" 117 | energy_account = self._get( 118 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_REPORTS}/{gw_id}/energyAccount" 119 | ) 120 | if energy_account is not None: 121 | return energy_account 122 | return dict() 123 | 124 | def get_consumptions_sequences(self, gw_id: str, usages: str) -> list[Any]: 125 | """Get consumption sequences for the device""" 126 | consumptions_sequences = self._get( 127 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_REPORTS}/{gw_id}/consSequencesApi8?usages={usages}" 128 | ) 129 | if consumptions_sequences is not None: 130 | return list(consumptions_sequences) 131 | return list() 132 | 133 | def get_consumptions_settings(self, gw_id: str) -> dict[str, Any]: 134 | """Get consumption settings""" 135 | consumptions_settings = self._post( 136 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{gw_id}/getConsumptionsSettings", 137 | {}, 138 | ) 139 | if consumptions_settings is not None: 140 | return consumptions_settings 141 | return dict() 142 | 143 | def set_consumptions_settings( 144 | self, 145 | gw_id: str, 146 | consumptions_settings: dict[str, Any], 147 | ) -> None: 148 | """Get consumption settings""" 149 | self._post( 150 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{gw_id}/consumptionsSettings", 151 | consumptions_settings, 152 | ) 153 | 154 | @staticmethod 155 | def get_items(features: dict[str, Any]) -> list[dict[str, int]]: 156 | """Get the Final[str] strings from DeviceProperies and ThermostatProperties""" 157 | items: list[dict[str, Any]] = DeviceProperties.list(0) 158 | for zone in features[DeviceFeatures.ZONES]: 159 | items.extend(ThermostatProperties.list(zone[ZoneAttribute.NUM])) 160 | return items 161 | 162 | def get_properties( 163 | self, gw_id: str, features: dict[str, Any], culture: str, umsys: str 164 | ) -> dict[str, Any]: 165 | """Get device properties""" 166 | properties = self._post( 167 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_DATA_ITEMS}/{gw_id}/get?umsys={umsys}", 168 | { 169 | "useCache": False, 170 | "items": self.get_items(features), 171 | "features": features, 172 | "culture": culture, 173 | }, 174 | ) 175 | if properties is not None: 176 | return properties 177 | return dict() 178 | 179 | def get_bsb_plant_data(self, gw_id: str) -> dict[str, Any]: 180 | """Get BSB plant data.""" 181 | data = self._get( 182 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.Bsb.value}/{gw_id}" 183 | ) 184 | if data is not None: 185 | return data 186 | return dict() 187 | 188 | def get_velis_plant_data(self, plant_data: PlantData, gw_id: str) -> dict[str, Any]: 189 | """Get Velis properties""" 190 | data = self._get(f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}") 191 | if data is not None: 192 | return data 193 | return dict() 194 | 195 | def get_velis_plant_settings( 196 | self, plant_data: PlantData, gw_id: str 197 | ) -> dict[str, Any]: 198 | """Get Velis settings""" 199 | settings = self._get( 200 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}/plantSettings" 201 | ) 202 | if settings is not None: 203 | return settings 204 | return dict() 205 | 206 | def get_menu_items(self, gw_id: str) -> list[dict[str, Any]]: 207 | """Get menu items""" 208 | items = self._get( 209 | f"{self.__api_url}{ARISTON_MENU_ITEMS}/{gw_id}?menuItems={MenuItemNames.values()}" 210 | ) 211 | if items is not None: 212 | return items 213 | return list() 214 | 215 | def set_property( 216 | self, 217 | gw_id: str, 218 | zone_id: int, 219 | features: dict[str, Any], 220 | device_property: str, 221 | value: float, 222 | prev_value: float, 223 | umsys: str, 224 | ) -> None: 225 | """Set device properties""" 226 | self._post( 227 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_DATA_ITEMS}/{gw_id}/set?umsys={umsys}", 228 | { 229 | "items": [ 230 | { 231 | "id": device_property, 232 | "prevValue": prev_value, 233 | "value": value, 234 | "zone": zone_id, 235 | } 236 | ], 237 | "features": features, 238 | }, 239 | ) 240 | 241 | def set_evo_number_of_showers(self, gw_id: str, number_of_showers: int) -> None: 242 | """Set Velis Evo number of showers""" 243 | self._post( 244 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.PD.value}/{gw_id}/showers", 245 | { 246 | "new": int(number_of_showers), 247 | }, 248 | ) 249 | 250 | def set_evo_mode(self, gw_id: str, value: WaterHeaterMode) -> None: 251 | """Set Velis Evo mode""" 252 | self._post( 253 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/mode", 254 | { 255 | "new": value.value, 256 | }, 257 | ) 258 | 259 | def set_lydos_mode(self, gw_id: str, value: LydosPlantMode) -> None: 260 | """Set Velis Lydos mode""" 261 | self._post( 262 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Se.value}/{gw_id}/mode", 263 | { 264 | "new": value.value, 265 | }, 266 | ) 267 | 268 | def set_nuos_mode(self, gw_id: str, value: NuosSplitOperativeMode) -> None: 269 | """Set Velis Nuos mode""" 270 | self._post( 271 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Slp.value}/{gw_id}/operativeMode", 272 | { 273 | "new": value.value, 274 | }, 275 | ) 276 | 277 | def set_bsb_mode(self, gw_id: str, value: BsbOperativeMode) -> None: 278 | """Set Bsb mode""" 279 | self._post( 280 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.Bsb.value}/{gw_id}/dhwMode", 281 | { 282 | "new": value.value, 283 | }, 284 | ) 285 | 286 | def set_bsb_zone_mode( 287 | self, 288 | gw_id: str, 289 | zone: int, 290 | value: BsbZoneMode, 291 | old_value: BsbZoneMode, 292 | is_cooling: bool, 293 | ) -> None: 294 | """Set Bsb zone mode""" 295 | self._post( 296 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_BSB_ZONES}/{gw_id}/{zone}/mode?isCooling={is_cooling}", 297 | {"new": value.value, "old": old_value.value}, 298 | ) 299 | 300 | def set_evo_temperature(self, gw_id: str, value: float) -> None: 301 | """Set Velis Evo temperature""" 302 | self._post( 303 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/temperature", 304 | { 305 | "new": value, 306 | }, 307 | ) 308 | 309 | def set_lydos_temperature(self, gw_id: str, value: float) -> None: 310 | """Set Velis Lydos temperature""" 311 | self._post( 312 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Se.value}/{gw_id}/temperature", 313 | { 314 | "new": value, 315 | }, 316 | ) 317 | 318 | def set_nuos_temperature( 319 | self, 320 | gw_id: str, 321 | comfort: float, 322 | reduced: float, 323 | old_comfort: Optional[float], 324 | old_reduced: Optional[float], 325 | ) -> None: 326 | """Set Nuos temperature""" 327 | self._post( 328 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Slp.value}/{gw_id}/temperatures", 329 | { 330 | "new": { 331 | "comfort": comfort, 332 | "reduced": reduced, 333 | }, 334 | "old": { 335 | "comfort": old_comfort, 336 | "reduced": old_reduced, 337 | }, 338 | }, 339 | ) 340 | 341 | def set_bsb_temperature( 342 | self, 343 | gw_id: str, 344 | comfort: float, 345 | reduced: float, 346 | old_comfort: Optional[float], 347 | old_reduced: Optional[float], 348 | ) -> None: 349 | """Set Bsb temperature""" 350 | self._post( 351 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.Bsb.value}/{gw_id}/dhwTemp", 352 | { 353 | "new": { 354 | "comf": comfort, 355 | "econ": reduced, 356 | }, 357 | "old": { 358 | "comf": old_comfort, 359 | "econ": old_reduced, 360 | }, 361 | }, 362 | ) 363 | 364 | def set_bsb_zone_temperature( 365 | self, 366 | gw_id: str, 367 | zone: int, 368 | comfort: float, 369 | reduced: float, 370 | old_comfort: Optional[float], 371 | old_reduced: Optional[float], 372 | is_cooling: bool, 373 | ) -> None: 374 | """Set Bsb zone temperature""" 375 | self._post( 376 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_BSB_ZONES}/{gw_id}/{zone}/temperatures?isCooling={is_cooling}", 377 | { 378 | "new": { 379 | "comf": comfort, 380 | "econ": reduced, 381 | }, 382 | "old": { 383 | "comf": old_comfort, 384 | "econ": old_reduced, 385 | }, 386 | }, 387 | ) 388 | 389 | def set_nous_boost(self, gw_id: str, boost: bool) -> None: 390 | """ "Set Nous boost""" 391 | self._post( 392 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Slp.value}/{gw_id}/boost", 393 | boost, 394 | ) 395 | 396 | def set_evo_eco_mode(self, gw_id: str, eco_mode: bool) -> None: 397 | """Set Velis Evo eco mode""" 398 | self._post( 399 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/switchEco", 400 | eco_mode, 401 | ) 402 | 403 | def set_lux_power_option(self, gw_id: str, power_option: bool) -> None: 404 | """Set Velis Lux2 power option""" 405 | self._post( 406 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/switchPowerOption", 407 | power_option, 408 | ) 409 | 410 | def set_velis_power(self, plant_data: PlantData, gw_id: str, power: bool) -> None: 411 | """Set Velis power""" 412 | self._post( 413 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}/switch", 414 | power, 415 | ) 416 | 417 | def set_velis_plant_setting( 418 | self, 419 | plant_data: PlantData, 420 | gw_id: str, 421 | setting: str, 422 | value: float, 423 | old_value: float, 424 | ) -> None: 425 | """Set Velis plant setting""" 426 | self._post( 427 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}/plantSettings", 428 | {setting: {"new": value, "old": old_value}}, 429 | ) 430 | 431 | def get_thermostat_time_progs( 432 | self, gw_id: str, zone: int, umsys: str 433 | ) -> dict[str, Any]: 434 | """Get thermostat time programs""" 435 | thermostat_time_progs = self._get( 436 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_TIME_PROGS}/{gw_id}/ChZn{zone}?umsys={umsys}", 437 | ) 438 | if thermostat_time_progs is not None: 439 | return thermostat_time_progs 440 | return dict() 441 | 442 | def set_holiday( 443 | self, 444 | gw_id: str, 445 | holiday_end_date: Optional[str], 446 | ) -> None: 447 | """Set holidays""" 448 | self._post( 449 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.PD}/{gw_id}/holiday", 450 | { 451 | "new": holiday_end_date, 452 | }, 453 | ) 454 | 455 | def get_bus_errors(self, gw_id: str) -> list[Any]: 456 | """Get bus errors""" 457 | bus_errors = self._get( 458 | f"{self.__api_url}{ARISTON_BUS_ERRORS}?gatewayId={gw_id}&blockingOnly=False&culture=en-US" 459 | ) 460 | if bus_errors is not None: 461 | return list(bus_errors) 462 | return [] 463 | 464 | def __request( 465 | self, 466 | method: str, 467 | path: str, 468 | params: Optional[dict[str, Any]] = None, 469 | body: Any = None, 470 | is_retry: bool = False, 471 | ) -> Optional[dict[str, Any]]: 472 | """Request with requests""" 473 | headers: dict[str, Any] = { 474 | "User-Agent": self.__user_agent, 475 | "ar.authToken": self.__token, 476 | } 477 | 478 | _LOGGER.debug( 479 | "Request method %s, path: %s, params: %s", 480 | method, 481 | path, 482 | params, 483 | ) 484 | response = requests.request( 485 | method, path, params=params, json=body, headers=headers, timeout=30000 486 | ) 487 | if not response.ok: 488 | match response.status_code: 489 | case 405: 490 | if not is_retry: 491 | if self.connect(): 492 | return self.__request(method, path, params, body, True) 493 | raise ConnectionException("Login failed (password changed?)") 494 | raise ConnectionException("Invalid token") 495 | case 404: 496 | return None 497 | case 429: 498 | content = response.content.decode() 499 | raise ConnectionException(response.status_code, content) 500 | case _: 501 | if not is_retry: 502 | time.sleep(5) 503 | return self.__request(method, path, params, body, True) 504 | raise ConnectionException(response.status_code) 505 | 506 | if len(response.content) > 0: 507 | json = response.json() 508 | _LOGGER.debug("Response %s", json) 509 | return json 510 | 511 | return None 512 | 513 | def _post(self, path: str, body: Any) -> Any: 514 | """POST request""" 515 | return self.__request("POST", path, None, body) 516 | 517 | def _get(self, path: str, params: Optional[dict[str, Any]] = None) -> Any: 518 | """GET request""" 519 | return self.__request("GET", path, params, None) 520 | 521 | async def async_connect(self) -> bool: 522 | """Async login to ariston cloud and get token""" 523 | 524 | try: 525 | response = await self._async_post( 526 | f"{self.__api_url}{ARISTON_LOGIN}", 527 | {"usr": self.__username, "pwd": self.__password}, 528 | ) 529 | 530 | if response is None: 531 | return False 532 | 533 | self.__token = response["token"] 534 | 535 | return True 536 | 537 | except Exception as error: 538 | raise ConnectionException() from error 539 | 540 | async def async_get_detailed_devices(self) -> list[Any]: 541 | """Async get detailed cloud devices""" 542 | detailed_devices = await self._async_get( 543 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}" 544 | ) 545 | if detailed_devices is not None: 546 | return list(detailed_devices) 547 | return list() 548 | 549 | async def async_get_detailed_velis_devices(self) -> list[Any]: 550 | """Async get detailed cloud devices""" 551 | detailed_velis_devices = await self._async_get( 552 | f"{self.__api_url}{ARISTON_VELIS}/{ARISTON_PLANTS}" 553 | ) 554 | if detailed_velis_devices is not None: 555 | return list(detailed_velis_devices) 556 | return list() 557 | 558 | async def async_get_devices(self) -> list[Any]: 559 | """Async get cloud devices""" 560 | devices = await self._async_get( 561 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{ARISTON_LITE}" 562 | ) 563 | if devices is not None: 564 | return list(devices) 565 | return list() 566 | 567 | async def async_get_features_for_device( 568 | self, gw_id: str 569 | ) -> Optional[dict[str, Any]]: 570 | """Async get features for the device""" 571 | return await self._async_get( 572 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{gw_id}/features" 573 | ) 574 | 575 | async def async_get_energy_account(self, gw_id: str) -> dict[str, Any]: 576 | """Async get energy account for the device""" 577 | energy_account = await self._async_get( 578 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_REPORTS}/{gw_id}/energyAccount" 579 | ) 580 | if energy_account is not None: 581 | return energy_account 582 | return dict() 583 | 584 | async def async_get_consumptions_sequences( 585 | self, gw_id: str, usages: str 586 | ) -> list[Any]: 587 | """Async get consumption sequences for the device""" 588 | consumptions_sequences = await self._async_get( 589 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_REPORTS}/{gw_id}/consSequencesApi8?usages={usages}" 590 | ) 591 | if consumptions_sequences is not None: 592 | return list(consumptions_sequences) 593 | return list() 594 | 595 | async def async_get_consumptions_settings(self, gw_id: str) -> dict[str, Any]: 596 | """Async get consumption settings""" 597 | consumptions_settings = await self._async_post( 598 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{gw_id}/getConsumptionsSettings", 599 | {}, 600 | ) 601 | if consumptions_settings is not None: 602 | return consumptions_settings 603 | return dict() 604 | 605 | async def async_set_consumptions_settings( 606 | self, 607 | gw_id: str, 608 | consumptions_settings: dict[str, Any], 609 | ) -> None: 610 | """Async set consumption settings""" 611 | await self._async_post( 612 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_PLANTS}/{gw_id}/consumptionsSettings", 613 | consumptions_settings, 614 | ) 615 | 616 | async def async_get_properties( 617 | self, gw_id: str, features: dict[str, Any], culture: str, umsys: str 618 | ) -> dict[str, Any]: 619 | """Async get device properties""" 620 | properties = await self._async_post( 621 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_DATA_ITEMS}/{gw_id}/get?umsys={umsys}", 622 | { 623 | "useCache": False, 624 | "items": self.get_items(features), 625 | "features": features, 626 | "culture": culture, 627 | }, 628 | ) 629 | if properties is not None: 630 | return properties 631 | return dict() 632 | 633 | async def async_get_bsb_plant_data(self, gw_id: str) -> dict[str, Any]: 634 | """Get BSB plant data.""" 635 | data = await self._async_get( 636 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.Bsb.value}/{gw_id}" 637 | ) 638 | if data is not None: 639 | return data 640 | return dict() 641 | 642 | async def async_get_velis_plant_data( 643 | self, plant_data: PlantData, gw_id: str 644 | ) -> dict[str, Any]: 645 | """Async get Velis properties""" 646 | med_plant_data = await self._async_get( 647 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}" 648 | ) 649 | if med_plant_data is not None: 650 | return med_plant_data 651 | return dict() 652 | 653 | async def async_get_velis_plant_settings( 654 | self, plant_data: PlantData, gw_id: str 655 | ) -> dict[str, Any]: 656 | """Async get Velis settings""" 657 | med_plant_settings = await self._async_get( 658 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}/plantSettings" 659 | ) 660 | if med_plant_settings is not None: 661 | return med_plant_settings 662 | return dict() 663 | 664 | async def async_get_menu_items(self, gw_id: str) -> list[dict[str, Any]]: 665 | """Async get menu items""" 666 | items = await self._async_get( 667 | f"{self.__api_url}{ARISTON_MENU_ITEMS}/{gw_id}?menuItems={MenuItemNames.values()}" 668 | ) 669 | if items is not None: 670 | return items 671 | return list() 672 | 673 | async def async_set_property( 674 | self, 675 | gw_id: str, 676 | zone_id: int, 677 | features: dict[str, Any], 678 | device_property: str, 679 | value: float, 680 | prev_value: float, 681 | umsys: str, 682 | ) -> None: 683 | """Async set device properties""" 684 | await self._async_post( 685 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_DATA_ITEMS}/{gw_id}/set?umsys={umsys}", 686 | { 687 | "items": [ 688 | { 689 | "id": device_property, 690 | "prevValue": prev_value, 691 | "value": value, 692 | "zone": zone_id, 693 | } 694 | ], 695 | "features": features, 696 | }, 697 | ) 698 | 699 | async def async_set_evo_number_of_showers( 700 | self, gw_id: str, number_of_showers: int 701 | ) -> None: 702 | """Set Velis Evo number of showers""" 703 | await self._async_post( 704 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.PD.value}/{gw_id}/showers", 705 | { 706 | "new": int(number_of_showers), 707 | }, 708 | ) 709 | 710 | async def async_set_evo_mode(self, gw_id: str, value: WaterHeaterMode) -> None: 711 | """Async set Velis Evo mode""" 712 | await self._async_post( 713 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/mode", 714 | { 715 | "new": value.value, 716 | }, 717 | ) 718 | 719 | async def async_set_lydos_mode(self, gw_id: str, value: LydosPlantMode) -> None: 720 | """Async set Velis Lydos mode""" 721 | await self._async_post( 722 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Se.value}/{gw_id}/mode", 723 | { 724 | "new": value.value, 725 | }, 726 | ) 727 | 728 | async def async_set_nuos_mode( 729 | self, gw_id: str, value: NuosSplitOperativeMode 730 | ) -> None: 731 | """Async set Velis Nuos mode""" 732 | await self._async_post( 733 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Slp.value}/{gw_id}/operativeMode", 734 | { 735 | "new": value.value, 736 | }, 737 | ) 738 | 739 | async def async_set_bsb_mode(self, gw_id: str, value: BsbOperativeMode) -> None: 740 | """Async set Bsb mode""" 741 | await self._async_post( 742 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.Bsb.value}/{gw_id}/dhwMode", 743 | { 744 | "new": value.value, 745 | }, 746 | ) 747 | 748 | async def async_set_bsb_zone_mode( 749 | self, 750 | gw_id: str, 751 | zone: int, 752 | value: BsbZoneMode, 753 | old_value: BsbZoneMode, 754 | is_cooling: bool, 755 | ) -> None: 756 | """Async set Bsb zone mode""" 757 | await self._async_post( 758 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_BSB_ZONES}/{gw_id}/{zone}/mode?isCooling={is_cooling}", 759 | {"new": value.value, "old": old_value.value}, 760 | ) 761 | 762 | async def async_set_evo_temperature(self, gw_id: str, value: float) -> None: 763 | """Async set Velis Evo temperature""" 764 | await self._async_post( 765 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/temperature", 766 | { 767 | "new": value, 768 | }, 769 | ) 770 | 771 | async def async_set_lydos_temperature(self, gw_id: str, value: float) -> None: 772 | """Async set Velis Lydos temperature""" 773 | await self._async_post( 774 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Se.value}/{gw_id}/temperature", 775 | { 776 | "new": value, 777 | }, 778 | ) 779 | 780 | async def async_set_nuos_temperature( 781 | self, 782 | gw_id: str, 783 | comfort: float, 784 | reduced: float, 785 | old_comfort: Optional[float], 786 | old_reduced: Optional[float], 787 | ) -> None: 788 | """Async set Velis Lydos temperature""" 789 | await self._async_post( 790 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Slp.value}/{gw_id}/temperatures", 791 | { 792 | "new": { 793 | "comfort": comfort, 794 | "reduced": reduced, 795 | }, 796 | "old": { 797 | "comfort": old_comfort, 798 | "reduced": old_reduced, 799 | }, 800 | }, 801 | ) 802 | 803 | async def async_set_bsb_temperature( 804 | self, 805 | gw_id: str, 806 | comfort: float, 807 | reduced: float, 808 | old_comfort: Optional[float], 809 | old_reduced: Optional[float], 810 | ) -> None: 811 | """Async set Bsb temperature""" 812 | await self._async_post( 813 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.Bsb.value}/{gw_id}/dhwTemp", 814 | { 815 | "new": { 816 | "comf": comfort, 817 | "econ": reduced, 818 | }, 819 | "old": { 820 | "comf": old_comfort, 821 | "econ": old_reduced, 822 | }, 823 | }, 824 | ) 825 | 826 | async def async_set_bsb_zone_temperature( 827 | self, 828 | gw_id: str, 829 | zone: int, 830 | comfort: float, 831 | reduced: float, 832 | old_comfort: Optional[float], 833 | old_reduced: Optional[float], 834 | is_cooling: bool, 835 | ) -> None: 836 | """Async set Bsb zone temperature""" 837 | await self._async_post( 838 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_BSB_ZONES}/{gw_id}/{zone}/temperatures?isCooling={is_cooling}", 839 | { 840 | "new": { 841 | "comf": comfort, 842 | "econ": reduced, 843 | }, 844 | "old": { 845 | "comf": old_comfort, 846 | "econ": old_reduced, 847 | }, 848 | }, 849 | ) 850 | 851 | async def async_set_nous_boost(self, gw_id: str, boost: bool) -> None: 852 | """ "Set Nous boost""" 853 | await self._async_post( 854 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Slp.value}/{gw_id}/boost", 855 | boost, 856 | ) 857 | 858 | async def async_set_evo_eco_mode(self, gw_id: str, eco_mode: bool) -> None: 859 | """Async set Velis Evo eco mode""" 860 | await self._async_post( 861 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/switchEco", 862 | eco_mode, 863 | ) 864 | 865 | async def async_set_lux_power_option(self, gw_id: str, power_option: bool) -> None: 866 | """Set Velis Lux2 power option""" 867 | await self._async_post( 868 | f"{self.__api_url}{ARISTON_VELIS}/{PlantData.Med.value}/{gw_id}/switchPowerOption", 869 | power_option, 870 | ) 871 | 872 | async def async_set_velis_power( 873 | self, plant_data: PlantData, gw_id: str, power: bool 874 | ) -> None: 875 | """Async set Velis power""" 876 | await self._async_post( 877 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}/switch", 878 | power, 879 | ) 880 | 881 | async def async_set_velis_plant_setting( 882 | self, 883 | plant_data: PlantData, 884 | gw_id: str, 885 | setting: str, 886 | value: float, 887 | old_value: float, 888 | ) -> None: 889 | """Async set Velis Evo plant setting""" 890 | await self._async_post( 891 | f"{self.__api_url}{ARISTON_VELIS}/{plant_data.value}/{gw_id}/plantSettings", 892 | {setting: {"new": value, "old": old_value}}, 893 | ) 894 | 895 | async def async_get_thermostat_time_progs( 896 | self, gw_id: str, zone: int, umsys: str 897 | ) -> Optional[dict[str, Any]]: 898 | """Async get thermostat time programs""" 899 | return await self._async_get( 900 | f"{self.__api_url}{ARISTON_REMOTE}/{ARISTON_TIME_PROGS}/{gw_id}/ChZn{zone}?umsys={umsys}", 901 | ) 902 | 903 | async def async_set_holiday( 904 | self, 905 | gw_id: str, 906 | holiday_end_date: Optional[str], 907 | ) -> None: 908 | """Async set holidays""" 909 | 910 | await self._async_post( 911 | f"{self.__api_url}{ARISTON_REMOTE}/{PlantData.PD}/{gw_id}/holiday", 912 | { 913 | "new": holiday_end_date, 914 | }, 915 | ) 916 | 917 | async def async_get_bus_errors(self, gw_id: str) -> list[Any]: 918 | """Async get bus errors""" 919 | bus_errors = await self._async_get( 920 | f"{self.__api_url}{ARISTON_BUS_ERRORS}?gatewayId={gw_id}&blockingOnly=False&culture=en-US" 921 | ) 922 | if bus_errors is not None: 923 | return list(bus_errors) 924 | return [] 925 | 926 | async def __async_request( 927 | self, 928 | method: str, 929 | path: str, 930 | params: Optional[dict[str, Any]] = None, 931 | body: Any = None, 932 | is_retry: bool = False, 933 | ) -> Optional[dict[str, Any]]: 934 | """Async request with aiohttp""" 935 | headers: dict[str, Any] = { 936 | "User-Agent": self.__user_agent, 937 | "ar.authToken": self.__token, 938 | } 939 | 940 | _LOGGER.debug( 941 | "Request method %s, path: %s, params: %s", 942 | method, 943 | path, 944 | params, 945 | ) 946 | 947 | async with aiohttp.ClientSession() as session: 948 | response = await session.request( 949 | method, path, params=params, json=body, headers=headers 950 | ) 951 | 952 | if not response.ok: 953 | match response.status: 954 | case 405: 955 | if not is_retry: 956 | if await self.async_connect(): 957 | return await self.__async_request( 958 | method, path, params, body, True 959 | ) 960 | raise ConnectionException( 961 | "Login failed (password changed?)" 962 | ) 963 | raise ConnectionException("Invalid token") 964 | case 404: 965 | return None 966 | case 429: 967 | content = await response.content.read() 968 | raise ConnectionException(response.status, content) 969 | case _: 970 | if not is_retry: 971 | await asyncio.sleep(5) 972 | return await self.__async_request( 973 | method, path, params, body, True 974 | ) 975 | raise ConnectionException(response.status) 976 | 977 | if response.content_length and response.content_length > 0: 978 | json = await response.json() 979 | _LOGGER.debug("Response %s", json) 980 | return json 981 | 982 | return None 983 | 984 | async def _async_post(self, path: str, body: Any) -> Any: 985 | """Async POST request""" 986 | return await self.__async_request("POST", path, None, body) 987 | 988 | async def _async_get( 989 | self, path: str, params: Optional[dict[str, Any]] = None 990 | ) -> Any: 991 | """Async GET request""" 992 | return await self.__async_request("GET", path, params, None) 993 | -------------------------------------------------------------------------------- /ariston/base_device.py: -------------------------------------------------------------------------------- 1 | """Device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import datetime as dt 5 | import logging 6 | from abc import ABC, abstractmethod 7 | from typing import Any, Optional 8 | 9 | from .ariston_api import AristonAPI 10 | from .const import ( 11 | ConsumptionTimeInterval, 12 | ConsumptionType, 13 | CustomDeviceFeatures, 14 | DeviceFeatures, 15 | DeviceAttribute, 16 | GalevoDeviceAttribute, 17 | SystemType, 18 | VelisDeviceAttribute, 19 | WheType, 20 | ) 21 | 22 | _LOGGER = logging.getLogger(__name__) 23 | 24 | 25 | class AristonBaseDevice(ABC): 26 | """Class representing a physical device, it's state and properties.""" 27 | 28 | def __init__( 29 | self, 30 | api: AristonAPI, 31 | attributes: dict[str, Any], 32 | ) -> None: 33 | self.api = api 34 | self.attributes = attributes 35 | 36 | self.features: dict[str, Any] = dict() 37 | self.custom_features: dict[str, Any] = dict() 38 | self.consumptions_sequences: list[dict[str, Any]] = list() 39 | self.data: dict[str, Any] = dict() 40 | self.consumption_sequence_last_changed_utc: dt.datetime = ( 41 | dt.datetime.fromtimestamp(0, dt.UTC).replace(tzinfo=dt.timezone.utc) 42 | ) 43 | self.gw: str = self.attributes.get(DeviceAttribute.GW, "") 44 | self.bus_errors_list: list[dict[str, Any]] = [] 45 | 46 | @property 47 | @abstractmethod 48 | def consumption_type(self) -> str: 49 | """String to get consumption type""" 50 | 51 | def _get_consumptions_sequences(self) -> None: 52 | """Get consumption sequence""" 53 | self.consumptions_sequences = self.api.get_consumptions_sequences( 54 | self.gw, 55 | self.consumption_type, 56 | ) 57 | 58 | async def _async_get_consumptions_sequences(self) -> None: 59 | """Async get consumption sequence""" 60 | self.consumptions_sequences = await self.api.async_get_consumptions_sequences( 61 | self.gw, 62 | self.consumption_type, 63 | ) 64 | 65 | @property 66 | def system_type(self) -> SystemType: 67 | """Get device system type wrapper""" 68 | return SystemType(self.attributes.get(DeviceAttribute.SYS, SystemType.UNKNOWN)) 69 | 70 | @property 71 | def whe_type(self) -> WheType: 72 | """Get device whe type wrapper""" 73 | return WheType( 74 | self.attributes.get(VelisDeviceAttribute.WHE_TYPE, WheType.Unknown) 75 | ) 76 | 77 | @property 78 | def whe_model_type(self) -> int: 79 | """Get device whe model type wrapper""" 80 | return self.attributes.get(VelisDeviceAttribute.WHE_MODEL_TYPE, 0) 81 | 82 | @property 83 | def gateway(self) -> str: 84 | """Get device gateway wrapper""" 85 | return self.gw 86 | 87 | @property 88 | def has_metering(self) -> Optional[bool]: 89 | """Get device has metering wrapper""" 90 | return self.features.get(DeviceFeatures.HAS_METERING, None) 91 | 92 | @property 93 | def name(self) -> Optional[str]: 94 | """Get device name wrapper""" 95 | return self.attributes.get(DeviceAttribute.NAME, None) 96 | 97 | @property 98 | def has_dhw(self)-> Optional[bool]: 99 | """Get device has domestic hot water""" 100 | return self.custom_features.get(CustomDeviceFeatures.HAS_DHW, None) 101 | 102 | @property 103 | def dhw_mode_changeable(self) -> Optional[bool]: 104 | """Get device domestic hot water mode changeable wrapper""" 105 | return self.features.get(DeviceFeatures.DHW_MODE_CHANGEABLE, None) 106 | 107 | @property 108 | def serial_number(self) -> Optional[str]: 109 | """Get device serial number wrapper""" 110 | return self.attributes.get(DeviceAttribute.SN, None) 111 | 112 | @property 113 | def firmware_version(self) -> Optional[str]: 114 | """Get device firmware version wrapper""" 115 | return self.attributes.get(GalevoDeviceAttribute.FW_VER, None) 116 | 117 | @property 118 | def bus_errors(self) -> list[dict[str, Any]]: 119 | """Get bus errors list wrapper""" 120 | return self.bus_errors_list 121 | 122 | @property 123 | def hpmp_sys(self) -> Optional[bool]: 124 | """Get device heat pump multi power system""" 125 | return self.attributes.get(DeviceAttribute.HPMP_SYS, None) 126 | 127 | def get_features(self) -> None: 128 | """Get device features wrapper""" 129 | self.features = self.api.get_features_for_device(self.gw) 130 | 131 | async def async_get_features(self) -> None: 132 | """Async get device features wrapper""" 133 | features = await self.api.async_get_features_for_device(self.gw) 134 | if features is not None: 135 | self.features = features 136 | 137 | @property 138 | def water_heater_current_mode_text(self) -> str: 139 | """Get water heater current mode text""" 140 | mode = self.water_heater_mode_value 141 | if mode in self.water_heater_mode_options: 142 | index = self.water_heater_mode_options.index(mode) 143 | return self.water_heater_mode_operation_texts[index] 144 | return "UNKNOWN" 145 | 146 | @abstractmethod 147 | def update_state(self) -> None: 148 | """Update the device states from the cloud""" 149 | raise NotImplementedError 150 | 151 | @abstractmethod 152 | async def async_update_state(self) -> None: 153 | """Async update the device states from the cloud""" 154 | raise NotImplementedError 155 | 156 | @property 157 | @abstractmethod 158 | def water_heater_current_temperature(self) -> Optional[float]: 159 | """Abstract method for get water heater current temperature""" 160 | raise NotImplementedError 161 | 162 | @property 163 | @abstractmethod 164 | def water_heater_mode_operation_texts(self) -> list[str]: 165 | """Abstract method for get water heater operation texts""" 166 | raise NotImplementedError 167 | 168 | @property 169 | @abstractmethod 170 | def water_heater_mode_options(self) -> list[int]: 171 | """Abstract method for get water heater mode options""" 172 | raise NotImplementedError 173 | 174 | @property 175 | @abstractmethod 176 | def water_heater_mode_value(self) -> Optional[int]: 177 | """Abstract method for get water heater mode value""" 178 | raise NotImplementedError 179 | 180 | @abstractmethod 181 | def set_water_heater_operation_mode(self, operation_mode: str) -> None: 182 | """Abstract method for set water heater operation mode""" 183 | raise NotImplementedError 184 | 185 | @abstractmethod 186 | async def async_set_water_heater_operation_mode(self, operation_mode: str) -> None: 187 | """Abstract method for async set water heater operation mode""" 188 | raise NotImplementedError 189 | 190 | @property 191 | def central_heating_total_energy_consumption(self) -> Any: 192 | """Get central heating total energy consumption""" 193 | return self._get_consumption_sequence_last_value( 194 | ConsumptionType.CENTRAL_HEATING_TOTAL_ENERGY, 195 | ConsumptionTimeInterval.LAST_DAY, 196 | ) 197 | 198 | @property 199 | def domestic_hot_water_total_energy_consumption(self) -> Any: 200 | """Get domestic hot water total energy consumption""" 201 | return self._get_consumption_sequence_last_value( 202 | ConsumptionType.DOMESTIC_HOT_WATER_TOTAL_ENERGY, 203 | ConsumptionTimeInterval.LAST_DAY, 204 | ) 205 | 206 | @property 207 | def central_heating_gas_consumption(self) -> Any: 208 | """Get central heating gas consumption""" 209 | return self._get_consumption_sequence_last_value( 210 | ConsumptionType.CENTRAL_HEATING_GAS, 211 | ConsumptionTimeInterval.LAST_DAY, 212 | ) 213 | 214 | @property 215 | def domestic_hot_water_heating_pump_electricity_consumption(self) -> Any: 216 | """Get domestic hot water heating pump electricity consumption""" 217 | return self._get_consumption_sequence_last_value( 218 | ConsumptionType.DOMESTIC_HOT_WATER_HEATING_PUMP_ELECTRICITY, 219 | ConsumptionTimeInterval.LAST_DAY, 220 | ) 221 | 222 | @property 223 | def domestic_hot_water_resistor_electricity_consumption(self) -> Any: 224 | """Get domestic hot water resistor electricity consumption""" 225 | return self._get_consumption_sequence_last_value( 226 | ConsumptionType.DOMESTIC_HOT_WATER_RESISTOR_ELECTRICITY, 227 | ConsumptionTimeInterval.LAST_DAY, 228 | ) 229 | 230 | @property 231 | def domestic_hot_water_gas_consumption(self) -> Any: 232 | """Get domestic hot water gas consumption""" 233 | return self._get_consumption_sequence_last_value( 234 | ConsumptionType.DOMESTIC_HOT_WATER_GAS, 235 | ConsumptionTimeInterval.LAST_DAY, 236 | ) 237 | 238 | @property 239 | def central_heating_electricity_consumption(self) -> Any: 240 | """Get central heating electricity consumption""" 241 | return self._get_consumption_sequence_last_value( 242 | ConsumptionType.CENTRAL_HEATING_ELECTRICITY, 243 | ConsumptionTimeInterval.LAST_DAY, 244 | ) 245 | 246 | @property 247 | def domestic_hot_water_electricity_consumption(self) -> Any: 248 | """Get domestic hot water electricity consumption""" 249 | return self._get_consumption_sequence_last_value( 250 | ConsumptionType.DOMESTIC_HOT_WATER_ELECTRICITY, 251 | ConsumptionTimeInterval.LAST_DAY, 252 | ) 253 | 254 | def _get_consumption_sequence_last_value( 255 | self, 256 | consumption_type: ConsumptionType, 257 | time_interval: ConsumptionTimeInterval, 258 | ) -> Any: 259 | """Get last value for consumption sequence""" 260 | for sequence in self.consumptions_sequences: 261 | if sequence["k"] == consumption_type.value and sequence["p"] == time_interval.value: 262 | return sequence["v"][-1] 263 | 264 | return None 265 | 266 | def _update_energy(self, old_consumptions_sequences: Optional[list[dict[str, Any]]]) -> None: 267 | """Update the device energy settings""" 268 | if ( 269 | self.custom_features.get( 270 | ConsumptionType.DOMESTIC_HOT_WATER_ELECTRICITY.name 271 | ) 272 | is None 273 | ): 274 | self._set_energy_features() 275 | 276 | if ( 277 | old_consumptions_sequences is not None 278 | and len(old_consumptions_sequences) > 0 279 | and old_consumptions_sequences != self.consumptions_sequences 280 | ): 281 | self.consumption_sequence_last_changed_utc = dt.datetime.now( 282 | dt.timezone.utc 283 | ) - dt.timedelta(hours=1) 284 | 285 | def update_energy(self) -> None: 286 | """Update the device energy settings from the cloud""" 287 | old_consumptions_sequences = self.consumptions_sequences 288 | self._get_consumptions_sequences() 289 | self._update_energy(old_consumptions_sequences) 290 | 291 | async def async_update_energy(self) -> None: 292 | """Async update the device energy settings from the cloud""" 293 | old_consumptions_sequences = self.consumptions_sequences 294 | await self._async_get_consumptions_sequences() 295 | self._update_energy(old_consumptions_sequences) 296 | 297 | def get_bus_errors(self) -> None: 298 | """Get bus errors from the cloud""" 299 | self.bus_errors_list = self.api.get_bus_errors(self.gw) 300 | 301 | async def async_get_bus_errors(self) -> None: 302 | """Async get bus errors from the cloud""" 303 | self.bus_errors_list = await self.api.async_get_bus_errors(self.gw) 304 | 305 | def _set_energy_features(self): 306 | """Set energy features""" 307 | for consumption_type in ConsumptionType: 308 | if ( 309 | self._get_consumption_sequence_last_value( 310 | consumption_type, 311 | ConsumptionTimeInterval.LAST_DAY, 312 | ) 313 | is not None 314 | ): 315 | self.custom_features[consumption_type.name] = True 316 | else: 317 | self.custom_features[consumption_type.name] = False 318 | 319 | def are_device_features_available( 320 | self, 321 | device_features: Optional[list[str]], 322 | system_types: Optional[list[SystemType]], 323 | whe_types: Optional[list[WheType]], 324 | ) -> bool: 325 | """Checks features availability""" 326 | if system_types is not None and self.system_type not in system_types: 327 | return False 328 | 329 | if whe_types is not None and self.whe_type not in whe_types: 330 | return False 331 | 332 | if device_features is not None: 333 | for device_feature in device_features: 334 | if ( 335 | self.features.get(device_feature) is not True 336 | and self.custom_features.get(device_feature) is not True 337 | and self.attributes.get(device_feature) is not True 338 | ): 339 | return False 340 | 341 | return True 342 | -------------------------------------------------------------------------------- /ariston/bsb_device.py: -------------------------------------------------------------------------------- 1 | """BSB device class for Ariston module.""" 2 | 3 | import logging 4 | from typing import Any, Optional 5 | 6 | from .const import ( 7 | BsbDeviceProperties, 8 | BsbOperativeMode, 9 | BsbZoneMode, 10 | BsbZoneProperties, 11 | CustomDeviceFeatures, 12 | PropertyType, 13 | ) 14 | from .device import AristonDevice 15 | 16 | _LOGGER = logging.getLogger(__name__) 17 | 18 | 19 | class AristonBsbDevice(AristonDevice): 20 | """Class representing a physical device, it's state and properties.""" 21 | 22 | @property 23 | def consumption_type(self) -> str: 24 | """String to get consumption type""" 25 | return "Ch%2CDhw" 26 | 27 | @property 28 | def plant_mode_supported(self) -> bool: 29 | """Returns whether plant mode is supported""" 30 | return False 31 | 32 | def update_state(self) -> None: 33 | """Update the device states from the cloud.""" 34 | self.data = self.api.get_bsb_plant_data(self.gw) 35 | 36 | async def async_update_state(self) -> None: 37 | """Async update the device states from the cloud.""" 38 | self.data = await self.api.async_get_bsb_plant_data(self.gw) 39 | 40 | def _get_features(self) -> None: 41 | self.custom_features[CustomDeviceFeatures.HAS_DHW] = True 42 | self.custom_features[CustomDeviceFeatures.HAS_OUTSIDE_TEMP] = True 43 | 44 | def get_features(self) -> None: 45 | """Get device features wrapper""" 46 | super().get_features() 47 | self._get_features() 48 | 49 | async def async_get_features(self) -> None: 50 | """Async get device features wrapper""" 51 | await super().async_get_features() 52 | self._get_features() 53 | 54 | @property 55 | def zone_numbers(self) -> list[int]: 56 | """Get zone number for device""" 57 | return [int(zone) for zone in self.zones] 58 | 59 | @property 60 | def zones(self) -> dict[str, dict[str, Any]]: 61 | """Get device zones wrapper""" 62 | return self.data.get(BsbDeviceProperties.ZONES, {}) 63 | 64 | def get_zone(self, zone: int) -> dict[str, Any]: 65 | """Get device zone""" 66 | return self.zones.get(str(zone), dict()) 67 | 68 | def get_zone_ch_comf_temp(self, zone: int) -> dict[str, Any]: 69 | """Get device zone central heating comfort temperature""" 70 | return self.get_zone(zone).get(BsbZoneProperties.CH_COMF_TEMP, dict[str, Any]()) 71 | 72 | def get_zone_ch_red_temp(self, zone: int) -> dict[str, Any]: 73 | """Get device zone central heating reduced temperature""" 74 | return self.get_zone(zone).get(BsbZoneProperties.CH_RED_TEMP, dict[str, Any]()) 75 | 76 | def get_zone_mode(self, zone: int) -> BsbZoneMode: 77 | """Get zone mode on value""" 78 | return BsbZoneMode( 79 | self.get_zone(zone) 80 | .get(BsbZoneProperties.MODE, dict[str, Any]()) 81 | .get(PropertyType.VALUE, None) or BsbZoneMode.UNDEFINED 82 | ) 83 | 84 | def get_zone_mode_options(self, zone: int) -> list[int]: 85 | """Get zone mode on options""" 86 | return ( 87 | self.get_zone(zone) 88 | .get(BsbZoneProperties.MODE, dict[str, Any]()) 89 | .get(PropertyType.ALLOWED_OPTIONS, None) 90 | ) 91 | 92 | @property 93 | def is_plant_in_heat_mode(self) -> bool: 94 | """Is the plant in a heat mode""" 95 | return not self.is_plant_in_cool_mode 96 | 97 | @property 98 | def is_plant_in_cool_mode(self) -> bool: 99 | """Is the plant in a cool mode""" 100 | return self.get_zone(self.zone_numbers[0]).get( 101 | BsbZoneProperties.COOLING_ON, False 102 | ) 103 | 104 | def is_zone_in_manual_mode(self, zone: int) -> bool: 105 | """Is zone in manual mode""" 106 | return self.get_zone_mode(zone) in [ 107 | BsbZoneMode.MANUAL, 108 | BsbZoneMode.MANUAL_NIGHT, 109 | ] 110 | 111 | def is_zone_in_time_program_mode(self, zone: int) -> bool: 112 | """Is zone in time program mode""" 113 | return self.get_zone_mode(zone) in [ 114 | BsbZoneMode.TIME_PROGRAM, 115 | ] 116 | 117 | def is_zone_mode_options_contains_manual(self, zone: int) -> bool: 118 | """Is zone mode options contains manual mode""" 119 | return BsbZoneMode.MANUAL.value in self.get_zone_mode_options( 120 | zone 121 | ) or BsbZoneMode.MANUAL_NIGHT.value in self.get_zone_mode_options(zone) 122 | 123 | def is_zone_mode_options_contains_time_program(self, zone: int) -> bool: 124 | """Is zone mode options contains time program mode""" 125 | return BsbZoneMode.TIME_PROGRAM.value in self.get_zone_mode_options(zone) 126 | 127 | def is_zone_mode_options_contains_off(self, zone: int) -> bool: 128 | """Is zone mode options contains off mode""" 129 | return BsbZoneMode.OFF.value in self.get_zone_mode_options(zone) 130 | 131 | @property 132 | def is_flame_on_value(self) -> bool: 133 | """Get is flame on value""" 134 | return self.data.get(BsbDeviceProperties.FLAME, False) 135 | 136 | @property 137 | def water_heater_current_temperature(self) -> Optional[float]: 138 | """Method for getting current water heater temperature.""" 139 | return self.data.get(BsbDeviceProperties.DHW_TEMP, None) 140 | 141 | @property 142 | def water_heater_minimum_temperature(self) -> float: 143 | """Method for getting water heater minimum temperature""" 144 | return self.data.get(BsbDeviceProperties.DHW_COMF_TEMP, dict[str, Any]()).get( 145 | PropertyType.MIN, None 146 | ) 147 | 148 | @property 149 | def water_heater_reduced_minimum_temperature(self) -> Optional[float]: 150 | """Get water heater reduced temperature""" 151 | return self.data.get(BsbDeviceProperties.DHW_REDU_TEMP, dict[str, Any]()).get( 152 | PropertyType.MIN, None 153 | ) 154 | 155 | @property 156 | def water_heater_target_temperature(self) -> Optional[float]: 157 | """Method for getting water heater target temperature""" 158 | return self.data.get(BsbDeviceProperties.DHW_COMF_TEMP, dict[str, Any]()).get( 159 | PropertyType.VALUE, None 160 | ) 161 | 162 | @property 163 | def water_heater_reduced_temperature(self) -> Optional[float]: 164 | """Get water heater reduced temperature""" 165 | return self.data.get(BsbDeviceProperties.DHW_REDU_TEMP, dict[str, Any]()).get( 166 | PropertyType.VALUE, None 167 | ) 168 | 169 | @property 170 | def water_heater_maximum_temperature(self) -> Optional[float]: 171 | """Method for getting water heater maximum temperature""" 172 | return self.data.get(BsbDeviceProperties.DHW_COMF_TEMP, dict[str, Any]()).get( 173 | PropertyType.MAX, None 174 | ) 175 | 176 | @property 177 | def water_heater_reduced_maximum_temperature(self) -> Optional[float]: 178 | """Get water heater reduced temperature""" 179 | return self.data.get(BsbDeviceProperties.DHW_REDU_TEMP, dict[str, Any]()).get( 180 | PropertyType.MAX, None 181 | ) 182 | 183 | @property 184 | def water_heater_temperature_step(self) -> int: 185 | """Method for getting water heater temperature step""" 186 | return self.data.get(BsbDeviceProperties.DHW_COMF_TEMP, dict[str, Any]()).get( 187 | PropertyType.STEP, None 188 | ) 189 | 190 | @property 191 | def water_heater_reduced_temperature_step(self) -> Optional[float]: 192 | """Get water heater reduced temperature""" 193 | return self.data.get(BsbDeviceProperties.DHW_REDU_TEMP, dict[str, Any]()).get( 194 | PropertyType.STEP, None 195 | ) 196 | 197 | @property 198 | def water_heater_temperature_decimals(self) -> int: 199 | """Method for getting water heater temperature decimals""" 200 | return 1 201 | 202 | @property 203 | def water_heater_temperature_unit(self) -> str: 204 | """Get water heater temperature unit""" 205 | return "°C" 206 | 207 | @property 208 | def water_heater_mode_operation_texts(self) -> list[str]: 209 | """Get water heater operation mode texts""" 210 | return [flag.name for flag in BsbOperativeMode] 211 | 212 | @property 213 | def water_heater_mode_options(self) -> list[int]: 214 | """Get water heater operation options""" 215 | return [flag.value for flag in BsbOperativeMode] 216 | 217 | @property 218 | def water_heater_mode_value(self) -> Optional[int]: 219 | """Method for getting water heater mode value""" 220 | return self.data.get(BsbDeviceProperties.DHW_MODE, dict[str, Any]()).get( 221 | PropertyType.VALUE, None 222 | ) 223 | 224 | def get_comfort_temp_min(self, zone: int) -> int: 225 | """Get zone comfort temp min""" 226 | return self.get_zone_ch_comf_temp(zone).get(PropertyType.MIN, 15) 227 | 228 | def get_comfort_temp_max(self, zone: int) -> int: 229 | """Get zone comfort temp max""" 230 | return self.get_zone_ch_comf_temp(zone).get(PropertyType.MAX, 24) 231 | 232 | def get_target_temp_step(self, zone: int) -> int: 233 | """Get target temp step""" 234 | return self.get_comfort_temp_step(zone) 235 | 236 | def get_comfort_temp_step(self, zone: int) -> int: 237 | """Get zone comfort temp step""" 238 | return self.get_zone_ch_comf_temp(zone).get(PropertyType.STEP, 0.5) 239 | 240 | def get_target_temp_value(self, zone: int) -> int: 241 | """Get target temp value""" 242 | return self.get_comfort_temp_value(zone) 243 | 244 | def get_comfort_temp_value(self, zone: int) -> int: 245 | """Get zone comfort temp value""" 246 | return self.get_zone_ch_comf_temp(zone).get(PropertyType.VALUE, 0) 247 | 248 | def get_reduced_temp_min(self, zone: int) -> int: 249 | """Get zone reduced temp min""" 250 | return self.get_zone_ch_red_temp(zone).get(PropertyType.MIN, 10) 251 | 252 | def get_reduced_temp_max(self, zone: int) -> int: 253 | """Get zone reduced temp max""" 254 | return self.get_zone_ch_red_temp(zone).get(PropertyType.MAX, 18) 255 | 256 | def get_reduced_temp_step(self, zone: int) -> int: 257 | """Get zone reduced temp step""" 258 | return self.get_zone_ch_red_temp(zone).get(PropertyType.STEP, 0.5) 259 | 260 | def get_reduced_temp_value(self, zone: int) -> int: 261 | """Get zone reduced temp value""" 262 | return self.get_zone_ch_red_temp(zone).get(PropertyType.VALUE, 0) 263 | 264 | def get_measured_temp_value(self, zone: int) -> int: 265 | """Get zone measured temp value""" 266 | return self.get_zone(zone).get(BsbZoneProperties.ROOM_TEMP, 0) 267 | 268 | def get_measured_temp_decimals(self, _) -> int: 269 | """Get zone measured temp decimals""" 270 | return 1 271 | 272 | def get_measured_temp_unit(self, _) -> str: 273 | """Get zone measured temp unit""" 274 | return "°C" 275 | 276 | def set_water_heater_temperature(self, temperature: float): 277 | """Set water heater temperature""" 278 | if len(self.data) == 0: 279 | self.update_state() 280 | reduced = self.water_heater_reduced_temperature 281 | if reduced is None: 282 | reduced = 0 283 | self._set_water_heater_temperature(temperature, reduced) 284 | 285 | async def async_set_water_heater_temperature(self, temperature: float): 286 | """Async set water heater temperature""" 287 | if len(self.data) == 0: 288 | await self.async_update_state() 289 | reduced = self.water_heater_reduced_temperature 290 | if reduced is None: 291 | reduced = 0 292 | await self._async_set_water_heater_temperature(temperature, reduced) 293 | 294 | def set_water_heater_operation_mode(self, operation_mode: str) -> None: 295 | """Set water heater operation mode""" 296 | self.api.set_bsb_mode(self.gw, BsbOperativeMode[operation_mode]) 297 | self.data[BsbDeviceProperties.DHW_MODE][PropertyType.VALUE] = BsbOperativeMode[ 298 | operation_mode 299 | ].value 300 | 301 | async def async_set_water_heater_operation_mode(self, operation_mode: str) -> None: 302 | """Async set water heater operation mode""" 303 | await self.api.async_set_bsb_mode(self.gw, BsbOperativeMode[operation_mode]) 304 | self.data[BsbDeviceProperties.DHW_MODE][PropertyType.VALUE] = BsbOperativeMode[ 305 | operation_mode 306 | ].value 307 | 308 | def set_water_heater_reduced_temperature(self, temperature: float): 309 | """Set water heater reduced temperature""" 310 | if len(self.data) == 0: 311 | self.update_state() 312 | current = self.water_heater_current_temperature 313 | if current is None: 314 | current = 0 315 | self._set_water_heater_temperature(current, temperature) 316 | 317 | async def async_set_water_heater_reduced_temperature(self, temperature: float): 318 | """Async set water heater temperature""" 319 | if len(self.data) == 0: 320 | await self.async_update_state() 321 | current = self.water_heater_current_temperature 322 | if current is None: 323 | current = 0 324 | await self._async_set_water_heater_temperature(current, temperature) 325 | 326 | def _set_water_heater_temperature(self, temperature: float, reduced: float): 327 | """Set water heater temperature""" 328 | self.api.set_bsb_temperature( 329 | self.gw, 330 | temperature, 331 | reduced, 332 | self.water_heater_target_temperature, 333 | self.water_heater_reduced_temperature, 334 | ) 335 | self.data[BsbDeviceProperties.DHW_COMF_TEMP][PropertyType.VALUE] = temperature 336 | self.data[BsbDeviceProperties.DHW_REDU_TEMP][PropertyType.VALUE] = reduced 337 | 338 | async def _async_set_water_heater_temperature( 339 | self, temperature: float, reduced: float 340 | ): 341 | """Async set water heater temperature""" 342 | await self.api.async_set_bsb_temperature( 343 | self.gw, 344 | temperature, 345 | reduced, 346 | self.water_heater_target_temperature, 347 | self.water_heater_reduced_temperature, 348 | ) 349 | self.data[BsbDeviceProperties.DHW_COMF_TEMP][PropertyType.VALUE] = temperature 350 | self.data[BsbDeviceProperties.DHW_REDU_TEMP][PropertyType.VALUE] = reduced 351 | 352 | def set_zone_mode(self, zone_mode: BsbZoneMode, zone: int): 353 | """Set zone mode""" 354 | self.api.set_bsb_zone_mode( 355 | self.gw, 356 | zone, 357 | zone_mode, 358 | self.get_zone_mode(zone), 359 | self.is_plant_in_cool_mode, 360 | ) 361 | self.get_zone(zone)[BsbZoneProperties.MODE][PropertyType.VALUE] = zone_mode 362 | 363 | async def async_set_zone_mode(self, zone_mode: BsbZoneMode, zone: int): 364 | """Async set zone mode""" 365 | await self.api.async_set_bsb_zone_mode( 366 | self.gw, 367 | zone, 368 | zone_mode, 369 | self.get_zone_mode(zone), 370 | self.is_plant_in_cool_mode, 371 | ) 372 | self.get_zone(zone)[BsbZoneProperties.MODE][PropertyType.VALUE] = zone_mode 373 | 374 | @property 375 | def outside_temp_value(self) -> str: 376 | """Get outside temperature value""" 377 | return self.data.get(BsbDeviceProperties.OUT_TEMP, 0) 378 | 379 | @property 380 | def outside_temp_unit(self) -> str: 381 | """Get outside temperature unit""" 382 | return "°C" 383 | 384 | def set_comfort_temp(self, temp: float, zone: int): 385 | """Set central heating comfort temp""" 386 | if len(self.data) == 0: 387 | self.update_state() 388 | reduced = self.get_reduced_temp_value(zone) 389 | self.api.set_bsb_zone_temperature( 390 | self.gw, 391 | zone, 392 | temp, 393 | reduced, 394 | self.get_comfort_temp_value(zone), 395 | self.get_reduced_temp_value(zone), 396 | self.is_plant_in_cool_mode, 397 | ) 398 | self.get_zone_ch_comf_temp(zone)[PropertyType.VALUE] = temp 399 | 400 | async def async_set_comfort_temp(self, temp: float, zone: int): 401 | """Async set central heating comfort temp""" 402 | if len(self.data) == 0: 403 | await self.async_update_state() 404 | reduced = self.get_reduced_temp_value(zone) 405 | await self.api.async_set_bsb_zone_temperature( 406 | self.gw, 407 | zone, 408 | temp, 409 | reduced, 410 | self.get_comfort_temp_value(zone), 411 | self.get_reduced_temp_value(zone), 412 | self.is_plant_in_cool_mode, 413 | ) 414 | self.get_zone_ch_comf_temp(zone)[PropertyType.VALUE] = temp 415 | 416 | def set_reduced_temp(self, temp: float, zone: int): 417 | """Set central heating reduced temp""" 418 | if len(self.data) == 0: 419 | self.update_state() 420 | comfort = self.get_comfort_temp_value(zone) 421 | self.api.set_bsb_zone_temperature( 422 | self.gw, 423 | zone, 424 | comfort, 425 | temp, 426 | self.get_comfort_temp_value(zone), 427 | self.get_reduced_temp_value(zone), 428 | self.is_plant_in_cool_mode, 429 | ) 430 | self.get_zone_ch_red_temp(zone)[PropertyType.VALUE] = temp 431 | 432 | async def async_set_reduced_temp(self, temp: float, zone: int): 433 | """Async set central heating reduced temp""" 434 | if len(self.data) == 0: 435 | await self.async_update_state() 436 | comfort = self.get_comfort_temp_value(zone) 437 | await self.api.async_set_bsb_zone_temperature( 438 | self.gw, 439 | zone, 440 | comfort, 441 | temp, 442 | self.get_comfort_temp_value(zone), 443 | self.get_reduced_temp_value(zone), 444 | self.is_plant_in_cool_mode, 445 | ) 446 | self.get_zone_ch_red_temp(zone)[PropertyType.VALUE] = temp 447 | -------------------------------------------------------------------------------- /ariston/const.py: -------------------------------------------------------------------------------- 1 | """Constants for ariston module""" 2 | 3 | from enum import Enum, IntEnum, StrEnum, unique 4 | from typing import Final, Any 5 | 6 | ARISTON_API_URL: Final[str] = "https://www.ariston-net.remotethermo.com/api/v2/" 7 | ARISTON_LOGIN: Final[str] = "accounts/login" 8 | ARISTON_REMOTE: Final[str] = "remote" 9 | ARISTON_VELIS: Final[str] = "velis" 10 | ARISTON_PLANTS: Final[str] = "plants" 11 | ARISTON_LITE: Final[str] = "lite" 12 | ARISTON_DATA_ITEMS: Final[str] = "dataItems" 13 | ARISTON_ZONES: Final[str] = "zones" 14 | ARISTON_BSB_ZONES: Final[str] = "bsbZones" 15 | ARISTON_REPORTS: Final[str] = "reports" 16 | ARISTON_TIME_PROGS: Final[str] = "timeProgs" 17 | ARISTON_BUS_ERRORS: Final[str] = "busErrors" 18 | ARISTON_MENU_ITEMS: Final[str] = "menuItems" 19 | ARISTON_USER_AGENT: Final[str] = "RestSharp/106.11.7.0" 20 | 21 | 22 | class ExtendedStrEnum(StrEnum): 23 | """Extended Enum class""" 24 | 25 | @classmethod 26 | def list(cls, zn: int) -> list[dict[str, Any]]: 27 | """Return all enum values""" 28 | return [{"id": c.value, "zn": zn} for c in cls] 29 | 30 | 31 | @unique 32 | class PlantData(StrEnum): 33 | """Plant data enum""" 34 | 35 | PD = "plantData" 36 | Med = "medPlantData" 37 | Se = "sePlantData" 38 | Slp = "slpPlantData" 39 | Bsb = "bsbPlantData" 40 | 41 | 42 | @unique 43 | class PlantMode(Enum): 44 | """Plant mode enum""" 45 | 46 | UNDEFINED = -1 47 | SUMMER = 0 48 | WINTER = 1 49 | HEATING_ONLY = 2 50 | COOLING = 3 51 | COOLING_ONLY = 4 52 | OFF = 5 53 | HOLIDAY = 6 54 | 55 | 56 | @unique 57 | class ZoneMode(Enum): 58 | """Zone mode enum""" 59 | 60 | UNDEFINED = -1 61 | OFF = 0 62 | MANUAL_NIGHT = 1 63 | MANUAL = 2 64 | TIME_PROGRAM = 3 65 | 66 | 67 | @unique 68 | class BsbZoneMode(Enum): 69 | """BSB zone mode enum""" 70 | 71 | UNDEFINED = -1 72 | OFF = 0 73 | TIME_PROGRAM = 1 74 | MANUAL_NIGHT = 2 75 | MANUAL = 3 76 | 77 | 78 | @unique 79 | class DhwMode(Enum): 80 | """Dhw mode enum""" 81 | 82 | DISABLED = 0 83 | TIME_BASED = 1 84 | ALWAYS_ACTIVE = 2 85 | HC_HP = 3 86 | HC_HP_40 = 4 87 | GREEN = 5 88 | 89 | 90 | @unique 91 | class Weather(Enum): 92 | """Weather enum""" 93 | 94 | UNAVAILABLE = 0 95 | CLEAR = 1 96 | VARIABLE = 2 97 | CLOUDY = 3 98 | RAINY = 4 99 | RAINSTORM = 5 100 | SNOW = 6 101 | FOG = 7 102 | WINDY = 8 103 | CLEAR_BY_NIGHT = 129 104 | VARIABLE_BY_NIGHT = 130 105 | 106 | 107 | @unique 108 | class GasEnergyUnit(IntEnum): 109 | """Gas energy unit enum""" 110 | 111 | KWH = 0 112 | GIGA_JOULE = 1 113 | THERM = 2 114 | MEGA_BTU = 3 115 | SMC = 4 116 | CUBE_METER = 5 117 | 118 | 119 | @unique 120 | class GasType(IntEnum): 121 | """Gas type enum""" 122 | 123 | NATURAL_GAS = 0 124 | LPG = 1 125 | AIR_PROPANED = 2 126 | GPO = 3 127 | PROPANE = 4 128 | 129 | 130 | @unique 131 | class Currency(IntEnum): 132 | """Currency enum""" 133 | 134 | ARS = 1 135 | EUR = 2 136 | BYN = 3 137 | CNY = 4 138 | HRK = 5 139 | CZK = 6 140 | DKK = 7 141 | HKD = 8 142 | HUF = 9 143 | IRR = 10 144 | KZT = 11 145 | CHF = 12 146 | MOP = 13 147 | PLZ = 14 148 | RON = 15 149 | RUB = 16 150 | TRY = 17 151 | UAH = 18 152 | GBP = 19 153 | USD = 20 154 | 155 | 156 | @unique 157 | class SystemType(Enum): 158 | """System type enum""" 159 | 160 | UNKNOWN = -1 161 | GALILEO1 = 1 162 | GALILEO2 = 2 163 | GALEVO = 3 164 | VELIS = 4 165 | BSB = 5 166 | 167 | 168 | @unique 169 | class Brands(Enum): 170 | """Brands enum""" 171 | 172 | Ariston = 1 173 | Chaffoteaux = 2 174 | Elco = 3 175 | Atag = 4 176 | Nti = 5 177 | Htp = 6 178 | Racold = 7 179 | 180 | 181 | class WaterHeaterMode(Enum): 182 | """Base class for water heater modes""" 183 | 184 | 185 | @unique 186 | class BsbOperativeMode(WaterHeaterMode): 187 | """BSB operative mode enum""" 188 | 189 | OFF = 0 190 | ON = 1 191 | 192 | 193 | @unique 194 | class LuxPlantMode(WaterHeaterMode): 195 | """Lux plant mode enum""" 196 | 197 | MANUAL = 1 198 | PROGRAM = 5 199 | BOOST = 9 200 | 201 | 202 | @unique 203 | class EvoPlantMode(WaterHeaterMode): 204 | """Evo plant mode enum""" 205 | 206 | MANUAL = 1 207 | PROGRAM = 5 208 | 209 | 210 | @unique 211 | class VelisPlantMode(WaterHeaterMode): 212 | """Velis plant mode enum""" 213 | 214 | MANUAL = 1 215 | PROGRAM = 5 216 | NIGHT = 8 217 | 218 | 219 | @unique 220 | class NuosSplitPlantMode(Enum): 221 | """NuosSplit plant mode enum""" 222 | 223 | MANUAL = 1 224 | PROGRAM = 2 225 | 226 | 227 | @unique 228 | class NuosSplitOperativeMode(WaterHeaterMode): 229 | """NuosSplit operative mode enum""" 230 | 231 | GREEN = 0 232 | COMFORT = 1 233 | FAST = 2 234 | IMEMORY = 3 235 | 236 | 237 | @unique 238 | class LydosPlantMode(WaterHeaterMode): 239 | """Lydos hybrid plant mode enum""" 240 | 241 | IMEMORY = 1 242 | GREEN = 2 243 | PROGRAM = 6 244 | BOOST = 7 245 | 246 | 247 | @unique 248 | class WheType(Enum): 249 | """Whe type enum""" 250 | 251 | Unknown = -1 252 | Evo = 1 253 | LydosHybrid = 2 254 | Lydos = 3 255 | NuosSplit = 4 256 | Andris2 = 5 257 | Evo2 = 6 258 | Lux2 = 7 259 | Lux = 8 260 | 261 | 262 | @unique 263 | class ConsumptionType(Enum): 264 | """Consumption type""" 265 | 266 | CENTRAL_HEATING_TOTAL_ENERGY = 1 267 | DOMESTIC_HOT_WATER_TOTAL_ENERGY = 2 268 | CENTRAL_COOLING_TOTAL_ENERGY = 3 269 | CENTRAL_HEATING_GAS = 7 270 | DOMESTIC_HOT_WATER_HEATING_PUMP_ELECTRICITY = 8 271 | DOMESTIC_HOT_WATER_RESISTOR_ELECTRICITY = 9 272 | DOMESTIC_HOT_WATER_GAS = 10 273 | CENTRAL_HEATING_ELECTRICITY = 20 274 | DOMESTIC_HOT_WATER_ELECTRICITY = 21 275 | 276 | 277 | @unique 278 | class ConsumptionTimeInterval(Enum): 279 | """Consumption time interval""" 280 | 281 | # I am not sure. This is just a guess. 282 | 283 | LAST_DAY = 1 284 | LAST_WEEK = 2 285 | LAST_MONTH = 3 286 | LAST_YEAR = 4 287 | 288 | 289 | class DeviceAttribute: 290 | """Constants for device attributes""" 291 | 292 | GW: Final[str] = "gw" # gwId 293 | HPMP_SYS: Final[str] = "hpmpSys" 294 | IS_OFFLINE_48H: Final[str] = "isOffline48H" 295 | LNK: Final[str] = "lnk" # gwLink 296 | LOC: Final[str] = "loc" # location 297 | CONSUMPTION_SETTINGS: Final[str] = "consumptionsSettings" 298 | GEOFENCE_CONFIG: Final[str] = "geofenceConfig" 299 | MQTT_API_VERSION: Final[str] = "mqttApiVersion" # mqttApiVersion 300 | NAME: Final[str] = "name" # plantName 301 | SN: Final[str] = "sn" # gwSerial 302 | SYS: Final[str] = "sys" # gwSysType 303 | TC_BY_GUEST: Final[str] = "tcByGuest" # controlledByGuest 304 | UTC_OFT: Final[str] = "utcOft" 305 | WEATHER_PROVIDER: Final[str] = "weatherProvider" 306 | 307 | 308 | class GalevoDeviceAttribute(DeviceAttribute): 309 | """Constants for galevo device attributes""" 310 | 311 | ZONES: Final[str] = "zones" 312 | SOLAR: Final[str] = "solar" 313 | CONV_BOILER: Final[str] = "convBoiler" 314 | HYBRID_SYS: Final[str] = "hybridSys" 315 | DHW_PROG_SUPPORTED: Final[str] = "dhwProgSupported" 316 | VIRTUAL_ZONES: Final[str] = "virtualZones" 317 | HAS_VMC: Final[str] = "hasVmc" 318 | HAS_EXT_TP: Final[str] = "hasExtTP" # extendedTimeProg 319 | HAS_BOILER: Final[str] = "hasBoiler" 320 | PILOT_SUPPORTED: Final[str] = "pilotSupported" 321 | UMSYS: Final[str] = "umsys" 322 | IS_VMC_R2: Final[str] = "isVmcR2" 323 | IS_EVO2: Final[str] = "isEvo2" 324 | FW_VER: Final[str] = "fwVer" 325 | 326 | 327 | class VelisDeviceAttribute(DeviceAttribute): 328 | """Constants for velis device attributes""" 329 | 330 | NOTIFY_ON_CONDENSATE_TANK_FULL: Final[str] = "notifyOnCondensateTankFull" 331 | NOTIFY_ON_ERRORS: Final[str] = "notifyOnErrors" 332 | NOTIFY_ON_READY_SHOWERS: Final[str] = "notifyOnReadyShowers" 333 | WHE_MODEL_TYPE: Final[str] = "wheModelType" 334 | WHE_TYPE: Final[str] = "wheType" 335 | 336 | 337 | class ZoneAttribute: 338 | """Constants for zone attributes""" 339 | 340 | NUM: Final[str] = "num" 341 | NAME: Final[str] = "name" 342 | ROOM_SENS: Final[str] = "roomSens" 343 | GEOFENCE_DEROGA: Final[str] = "geofenceDeroga" 344 | IS_HIDDEN: Final[str] = "isHidden" 345 | 346 | 347 | class CustomDeviceFeatures: 348 | """Constants for custom device features""" 349 | 350 | HAS_DHW: Final[str] = "hasDhw" 351 | HAS_OUTSIDE_TEMP: Final[str] = "hasOutsideTemp" 352 | 353 | 354 | class DeviceFeatures: 355 | """Constants for device features""" 356 | 357 | AUTO_THERMO_REG: Final[str] = "autoThermoReg" 358 | BMS_ACTIVE: Final[str] = "bmsActive" 359 | BUFFER_TIME_PROG_AVAILABLE: Final[str] = "bufferTimeProgAvailable" 360 | CASCDE_SYS: Final[str] = "cascadeSys" 361 | CONV_BOILER: Final[str] = "convBoiler" 362 | DHW_BOILER_PRESENT: Final[str] = "dhwBoilerPresent" 363 | DHW_HIDDEN: Final[str] = "dhwHidden" 364 | DHW_MODE_CHANGEABLE: Final[str] = "dhwModeChangeable" 365 | DHW_PROG_SUPPORTED: Final[str] = "dhwProgSupported" 366 | DICTINCT_HEAT_COOL_SETPOINT: Final[str] = "distinctHeatCoolSetpoints" 367 | EXTENDED_TIME_PROG: Final[str] = "extendedTimeProg" 368 | HAS_BOILER: Final[str] = "hasBoiler" 369 | HAS_EM20: Final[str] = "hasEm20" 370 | HAS_FIREPLACE: Final[str] = "hasFireplace" 371 | HAS_METERING: Final[str] = "hasMetering" 372 | HAS_SLP: Final[str] = "hasSlp" # Low Pressure Pump 373 | HAS_TWO_COOLING_TEMP: Final[str] = "hasTwoCoolingTemp" 374 | HAS_VMC: Final[str] = "hasVmc" 375 | HAS_ZONE_NAMES: Final[str] = "hasZoneNames" 376 | HP_CASCADE_CONFIG: Final[str] = "hpCascadeConfig" 377 | HP_CASCADE_SYS: Final[str] = "hpCascadeSys" 378 | HP_SYS: Final[str] = "hpSys" 379 | HV_INPUT_OFF: Final[str] = "hvInputOff" 380 | HYBRID_SYS: Final[str] = "hybridSys" 381 | IS_EVO2: Final[str] = "isEvo2" 382 | IS_VMC_R2: Final[str] = "isVmcR2" 383 | PILOT_SUPPORTED: Final[str] = "pilotSupported" 384 | PRE_HEATING_SUPPORTED: Final[str] = "preHeatingSupported" 385 | SOLAR: Final[str] = "solar" 386 | VIRTUAL_ZONES: Final[str] = "virtualZones" 387 | ZONES: Final[str] = "zones" 388 | WEATHER_PROVIDER: Final[str] = "weatherProvider" 389 | 390 | 391 | class VelisBaseDeviceProperties: 392 | """Contants for VelisBase device properties""" 393 | 394 | GW: Final[str] = "gw" 395 | MODE: Final[str] = "mode" 396 | ON: Final[str] = "on" 397 | 398 | 399 | class EvoOneDeviceProperties(VelisBaseDeviceProperties): 400 | """Contants for Velis Evo device properties""" 401 | 402 | AV_SHW: Final[str] = "avShw" 403 | ECO: Final[str] = "eco" 404 | MAX_AV_SHW: Final[str] = "maxAvShw" 405 | MAX_REQ_SHW: Final[str] = "maxReqShw" 406 | PROC_REQ_SHW: Final[str] = "procReqShw" 407 | REQ_SHW: Final[str] = "reqShw" 408 | RM_TM: Final[str] = "rmTm" 409 | SHW_P1: Final[str] = "shwP1" 410 | STATE: Final[str] = "state" 411 | TEMP: Final[str] = "temp" 412 | TM_P1: Final[str] = "tmP1" 413 | 414 | 415 | class VelisDeviceProperties(VelisBaseDeviceProperties): 416 | """Contants for Velis device properties""" 417 | 418 | PROC_REQ_TEMP: Final[str] = "procReqTemp" 419 | 420 | 421 | class NuosSplitProperties(VelisDeviceProperties): 422 | """Constants for NuosSplit device properties""" 423 | 424 | WATER_TEMP: Final[str] = "waterTemp" 425 | COMFORT_TEMP: Final[str] = "comfortTemp" 426 | REDUCED_TEMP: Final[str] = "reducedTemp" 427 | OP_MODE: Final[str] = "opMode" 428 | BOOST_ON: Final[str] = "boostOn" 429 | HP_STATE: Final[str] = "hpState" 430 | 431 | 432 | class EvoLydosDeviceProperties(VelisDeviceProperties): 433 | """Constants for evo and lydos device properties""" 434 | 435 | ANTI_LEG: Final[str] = "antiLeg" 436 | AV_SHW: Final[str] = "avShw" 437 | HEAT_REQ: Final[str] = "heatReq" 438 | REQ_TEMP: Final[str] = "reqTemp" 439 | TEMP: Final[str] = "temp" 440 | 441 | 442 | class EvoDeviceProperties(EvoLydosDeviceProperties): 443 | """Contants for Velis Evo device properties""" 444 | 445 | ECO: Final[str] = "eco" 446 | PWR_OPT: Final[str] = "pwrOpt" 447 | RM_TM: Final[str] = "rmTm" 448 | 449 | 450 | class LydosDeviceProperties(EvoLydosDeviceProperties): 451 | """Contants for Velis Lydos device properties""" 452 | 453 | BOOST_REQ_TEMP: Final[str] = "boostReqTemp" 454 | 455 | 456 | class BsbDeviceProperties: 457 | """Constants for bsb device properties.""" 458 | 459 | DHW_COMF_TEMP: Final[str] = "dhwComfTemp" 460 | DHW_ENABLED: Final[str] = "dhwEnabled" 461 | DHW_MODE: Final[str] = "dhwMode" 462 | DHW_PROG_READ_ONLY: Final[str] = "dhwProgReadOnly" 463 | DHW_REDU_TEMP: Final[str] = "dhwReduTemp" 464 | DHW_STORAGE_TEMP_ERROR: Final[str] = "dhwStorageTempError" 465 | DHW_TEMP: Final[str] = "dhwTemp" 466 | FLAME: Final[str] = "flame" 467 | GW: Final[str] = "gw" 468 | HAS_DHW_TEMP: Final[str] = "hasDhwTemp" 469 | HAS_OUT_TEMP: Final[str] = "hasOutTemp" 470 | HP_ON: Final[str] = "hpOn" 471 | OUTSIDE_TEMP_ERROR: Final[str] = "outsideTempError" 472 | OUT_TEMP: Final[str] = "outTemp" 473 | ZONES: Final[str] = "zones" 474 | 475 | 476 | class BsbZoneProperties: 477 | """Constants for bsb zone properties.""" 478 | 479 | CH_COMF_TEMP: Final[str] = "chComfTemp" 480 | CH_PROT_TEMP: Final[str] = "chProtTemp" 481 | CH_RED_TEMP: Final[str] = "chRedTemp" 482 | COOL_COMF_TEMP: Final[str] = "coolComfTemp" 483 | COOLING_ON: Final[str] = "coolingOn" 484 | COOL_PROT_TEMP: Final[str] = "coolProtTemp" 485 | COOL_RED_TEMP: Final[str] = "coolRedTemp" 486 | DESIRED_ROOM_TEMP: Final[str] = "desiredRoomTemp" 487 | HAS_ROOM_SENS: Final[str] = "hasRoomSens" 488 | HEATING_ON: Final[str] = "heatingOn" 489 | HEAT_OR_COOL_REQ: Final[str] = "heatOrCoolReq" 490 | HOLIDAYS: Final[str] = "holidays" 491 | MODE: Final[str] = "mode" 492 | ROOM_TEMP: Final[str] = "roomTemp" 493 | ROOM_TEMP_ERROR: Final[str] = "roomTempError" 494 | USE_REDUCED_OPERATION_MODE_ON_HOLIDAY: Final[str] = ( 495 | "useReducedOperationModeOnHoliday" 496 | ) 497 | 498 | 499 | class MedDeviceSettings: 500 | """Constatns for Med device settings""" 501 | 502 | MED_ANTILEGIONELLA_ON_OFF: Final[str] = "MedAntilegionellaOnOff" 503 | MED_HEATING_RATE: Final[str] = "MedHeatingRate" 504 | MED_MAX_SETPOINT_TEMPERATURE: Final[str] = "MedMaxSetpointTemperature" 505 | MED_MAX_SETPOINT_TEMPERATURE_MAX: Final[str] = "MedMaxSetpointTemperatureMax" 506 | MED_MAX_SETPOINT_TEMPERATURE_MIN: Final[str] = "MedMaxSetpointTemperatureMin" 507 | 508 | 509 | class SeDeviceSettings: 510 | """Constatns for Se device settings""" 511 | 512 | SE_ANTILEGIONELLA_ON_OFF: Final[str] = "SeAntilegionellaOnOff" 513 | SE_ANTI_COOLING_ON_OFF: Final[str] = "SeAntiCoolingOnOff" 514 | SE_NIGHT_MODE_ON_OFF: Final[str] = "SeNightModeOnOff" 515 | SE_PERMANENT_BOOST_ON_OFF: Final[str] = "SePermanentBoostOnOff" 516 | SE_MAX_SETPOINT_TEMPERATURE: Final[str] = "SeMaxSetpointTemperature" 517 | SE_MAX_SETPOINT_TEMPERATURE_MAX: Final[str] = "SeMaxSetpointTemperatureMax" 518 | SE_MAX_SETPOINT_TEMPERATURE_MIN: Final[str] = "SeMaxSetpointTemperatureMin" 519 | SE_ANTI_COOLING_TEMPERATURE: Final[str] = "SeAntiCoolingTemperature" 520 | SE_ANTI_COOLING_TEMPERATURE_MAX: Final[str] = "SeAntiCoolingTemperatureMax" 521 | SE_ANTI_COOLING_TEMPERATURE_MIN: Final[str] = "SeAntiCoolingTemperatureMin" 522 | SE_MAX_GREEN_SETPOINT_TEMPERATURE: Final[str] = "SeMaxGreenSetpointTemperature" 523 | SE_HEATING_RATE: Final[str] = "SeHeatingRate" 524 | SE_NIGHT_BEGIN_AS_MINUTES: Final[str] = "SeNightBeginAsMinutes" 525 | SE_NIGHT_BEGIN_MIN_AS_MINUTES: Final[str] = "SeNightBeginMinAsMinutes" 526 | SE_NIGHT_BEGIN_MAX_AS_MINUTES: Final[str] = "SeNightBeginMaxAsMinutes" 527 | SE_NIGHT_END_AS_MINUTES: Final[str] = "SeNightEndAsMinutes" 528 | SE_NIGHT_END_MIN_AS_MINUTES: Final[str] = "SeNightEndMinAsMinutes" 529 | SE_NIGHT_END_MAX_AS_MINUTES: Final[str] = "SeNightEndMaxAsMinutes" 530 | 531 | 532 | class SlpDeviceSettings: 533 | """Constatns for Slp device settings""" 534 | 535 | SLP_MAX_GREEN_TEMPERATURE: Final[str] = "SlpMaxGreenTemperature" 536 | SLP_MAX_SETPOINT_TEMPERATURE: Final[str] = "SlpMaxSetpointTemperature" 537 | SLP_MAX_SETPOINT_TEMPERATURE_MIN: Final[str] = "SlpMaxSetpointTemperatureMin" 538 | SLP_MAX_SETPOINT_TEMPERATURE_MAX: Final[str] = "SlpMaxSetpointTemperatureMax" 539 | SLP_MIN_SETPOINT_TEMPERATURE: Final[str] = "SlpMinSetpointTemperature" 540 | SLP_MIN_SETPOINT_TEMPERATURE_MIN: Final[str] = "SlpMinSetpointTemperatureMin" 541 | SLP_MIN_SETPOINT_TEMPERATURE_MAX: Final[str] = "SlpMinSetpointTemperatureMax" 542 | SLP_ANTILEGIONELLA_ON_OFF: Final[str] = "SlpAntilegionellaOnOff" 543 | SLP_PRE_HEATING_ON_OFF: Final[str] = "SlpPreHeatingOnOff" 544 | SLP_HEATING_RATE: Final[str] = "SlpHeatingRate" 545 | SLP_HC_HP_MODE: Final[str] = "SlpHcHpMode" 546 | 547 | 548 | class DeviceProperties(ExtendedStrEnum): 549 | """Constants for device properties""" 550 | 551 | PLANT_MODE = "PlantMode" 552 | IS_FLAME_ON = "IsFlameOn" 553 | IS_HEATING_PUMP_ON = "IsHeatingPumpOn" 554 | HOLIDAY = "Holiday" 555 | OUTSIDE_TEMP = "OutsideTemp" 556 | WEATHER = "Weather" 557 | HEATING_CIRCUIT_PRESSURE = "HeatingCircuitPressure" 558 | CH_FLOW_TEMP = "ChFlowTemp" 559 | CH_FLOW_SETPOINT_TEMP = "ChFlowSetpointTemp" 560 | DHW_TEMP = "DhwTemp" 561 | DHW_STORAGE_TEMPERATURE = "DhwStorageTemperature" 562 | DHW_TIMEPROG_COMFORT_TEMP = "DhwTimeProgComfortTemp" 563 | DHW_TIMEPROG_ECONOMY_TEMP = "DhwTimeProgEconomyTemp" 564 | DHW_MODE = "DhwMode" 565 | AUTOMATIC_THERMOREGULATION = "AutomaticThermoregulation" 566 | ANTILEGIONELLA_ON_OFF = "AntilegionellaOnOff" 567 | ANTILEGIONELLA_TEMP = "AntilegionellaTemp" 568 | ANTILEGIONELLA_FREQ = "AntilegionellaFreq" 569 | HYBRID_MODE = "HybridMode" 570 | BUFFER_CONTROL_MODE = "BufferControlMode" 571 | BUFFER_TIME_PROG_COMFORT_HEATING_TEMP = "BufferTimeProgComfortHeatingTemp" 572 | BUFFER_TIME_PROG_ECONOMY_HEATING_TEMP = "BufferTimeProgEconomyHeatingTemp" 573 | BUFFER_TIME_PROG_COMFORT_COOLING_TEMP = "BufferTimeProgComfortCoolingTemp" 574 | BUFFER_TIME_PROG_ECONOMY_COOLING_TEMP = "BufferTimeProgEconomyCoolingTemp" 575 | IS_QUIET = "IsQuite" # ariston misspelled IsQuiet 576 | 577 | 578 | class ThermostatProperties(ExtendedStrEnum): 579 | """Constants for thermostat properties""" 580 | 581 | ZONE_MEASURED_TEMP = "ZoneMeasuredTemp" 582 | ZONE_DESIRED_TEMP = "ZoneDesiredTemp" 583 | ZONE_COMFORT_TEMP = "ZoneComfortTemp" 584 | ZONE_MODE = "ZoneMode" 585 | ZONE_HEAT_REQUEST = "ZoneHeatRequest" 586 | ZONE_ECONOMY_TEMP = "ZoneEconomyTemp" 587 | ZONE_DEROGA = "ZoneDeroga" 588 | ZONE_IS_ZONE_PILOT_ON = "IsZonePilotOn" 589 | ZONE_VIRT_TEMP_OFFSET_HEAT = "VirtTempOffsetHeat" 590 | HEATING_FLOW_TEMP = "HeatingFlowTemp" 591 | HEATING_FLOW_OFFSET = "HeatingFlowOffset" 592 | COOLING_FLOW_TEMP = "CoolingFlowTemp" 593 | COOLING_FLOW_OFFSET = "CoolingFlowOffset" 594 | ZONE_NAME = "ZoneName" 595 | VIRT_TEMP_SETPOINT_HEAT = "VirtTempSetpointHeat" 596 | VIRT_TEMP_SETPOINT_COOL = "VirtTempSetpointCool" 597 | VIRT_COMFORT_TEMP = "VirtComfortTemp" 598 | VIRT_REDUCED_TEMP = "VirtReducedTemp" 599 | ZONE_VIRT_TEMP_OFFSET_COOL = "VirtTempOffsetCool" 600 | 601 | 602 | class ConsumptionProperties: 603 | """Constants for consumption properties""" 604 | 605 | CURRENCY: Final[str] = "currency" 606 | GAS_TYPE: Final[str] = "gasType" 607 | GAS_ENERGY_UNIT: Final[str] = "gasEnergyUnit" 608 | ELEC_COST: Final[str] = "elecCost" 609 | GAS_COST: Final[str] = "gasCost" 610 | 611 | 612 | class PropertyType: 613 | """Constants for property types""" 614 | 615 | VALUE: Final[str] = "value" 616 | OPTIONS: Final[str] = "options" 617 | OPT_TEXTS: Final[str] = "optTexts" 618 | UNIT: Final[str] = "unit" 619 | MIN: Final[str] = "min" 620 | MAX: Final[str] = "max" 621 | STEP: Final[str] = "step" 622 | DECIMALS: Final[str] = "decimals" 623 | ZONE: Final[str] = "zone" 624 | EXPIRES_ON: Final[str] = "expiresOn" 625 | ALLOWED_OPTIONS: Final[str] = "allowedOptions" 626 | 627 | 628 | class BusErrorsProperties: 629 | """Constants for bus errors properties""" 630 | 631 | GW: Final[str] = "gw" 632 | TIMESTAMP: Final[str] = "timestamp" 633 | FAULT: Final[str] = "fault" 634 | MULT: Final[str] = "mult" 635 | CODE: Final[str] = "code" 636 | PRI: Final[str] = "pri" 637 | ERR_DEX: Final[str] = "errDex" 638 | RES: Final[str] = "res" 639 | BLK: Final[str] = "blk" 640 | 641 | 642 | class MenuItemNames(IntEnum): 643 | """Constants for menu items""" 644 | 645 | CH_RETURN_TEMP = 124 646 | SIGNAL_STRENGTH = 119 647 | 648 | @classmethod 649 | def values(cls) -> str: 650 | """Return all menu items values""" 651 | return ",".join(str(c.value) for c in cls) 652 | -------------------------------------------------------------------------------- /ariston/device.py: -------------------------------------------------------------------------------- 1 | """Device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from abc import ABC, abstractmethod 6 | from typing import Optional 7 | 8 | from .base_device import AristonBaseDevice 9 | 10 | _LOGGER = logging.getLogger(__name__) 11 | 12 | 13 | class AristonDevice(AristonBaseDevice, ABC): 14 | """Class representing a physical device, it's state and properties.""" 15 | 16 | @property 17 | @abstractmethod 18 | def water_heater_temperature_decimals(self) -> int: 19 | """Abstract method for get water heater temperature decimals""" 20 | raise NotImplementedError 21 | 22 | @property 23 | @abstractmethod 24 | def water_heater_temperature_unit(self) -> str: 25 | """Abstract method for get water heater temperature unit""" 26 | raise NotImplementedError 27 | 28 | @property 29 | @abstractmethod 30 | def water_heater_temperature_step(self) -> int: 31 | """Abstract method for get water heater temperature step""" 32 | raise NotImplementedError 33 | 34 | @property 35 | @abstractmethod 36 | def water_heater_minimum_temperature(self) -> float: 37 | """Abstract method for get water heater minimum temperature""" 38 | raise NotImplementedError 39 | 40 | @property 41 | @abstractmethod 42 | def water_heater_target_temperature(self) -> Optional[float]: 43 | """Abstract method for get water heater target temperature""" 44 | raise NotImplementedError 45 | 46 | @property 47 | @abstractmethod 48 | def water_heater_maximum_temperature(self) -> Optional[float]: 49 | """Abstract method for get water heater maximum temperature""" 50 | raise NotImplementedError 51 | 52 | @abstractmethod 53 | def set_water_heater_temperature(self, temperature: float) -> None: 54 | """Abstract method for set water temperature""" 55 | raise NotImplementedError 56 | 57 | @abstractmethod 58 | async def async_set_water_heater_temperature(self, temperature: float) -> None: 59 | """Abstract method for async set water temperature""" 60 | raise NotImplementedError 61 | -------------------------------------------------------------------------------- /ariston/evo_device.py: -------------------------------------------------------------------------------- 1 | """Evo device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from datetime import datetime 6 | from typing import Optional 7 | 8 | from .const import ( 9 | EvoPlantMode, 10 | EvoDeviceProperties, 11 | MedDeviceSettings, 12 | PlantData, 13 | WaterHeaterMode, 14 | ) 15 | from .evo_lydos_device import AristonEvoLydosDevice 16 | 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | 20 | class AristonEvoDevice(AristonEvoLydosDevice): 21 | """Class representing a physical device, it's state and properties.""" 22 | 23 | @property 24 | def plant_data(self) -> PlantData: 25 | """Final string to get plant data""" 26 | return PlantData.Med 27 | 28 | @property 29 | def anti_legionella_on_off(self) -> str: 30 | """Final string to get anti-legionella-on-off""" 31 | return MedDeviceSettings.MED_ANTILEGIONELLA_ON_OFF 32 | 33 | @property 34 | def consumption_type(self) -> str: 35 | """String to get consumption type""" 36 | return "Dhw" 37 | 38 | @property 39 | def water_heater_mode(self) -> type[WaterHeaterMode]: 40 | """Return the water heater mode class""" 41 | return EvoPlantMode 42 | 43 | @property 44 | def max_setpoint_temp(self) -> str: 45 | return MedDeviceSettings.MED_MAX_SETPOINT_TEMPERATURE 46 | 47 | @property 48 | def water_heater_eco_value(self) -> Optional[int]: 49 | """Get water heater eco value""" 50 | return self.data.get(EvoDeviceProperties.ECO, None) 51 | 52 | @property 53 | def rm_tm_value(self) -> Optional[str]: 54 | """Get remaining time value""" 55 | return self.data.get(EvoDeviceProperties.RM_TM, None) 56 | 57 | @property 58 | def rm_tm_in_minutes(self) -> int: 59 | """Get remaining time value in minutes""" 60 | rm_tm = self.rm_tm_value 61 | if rm_tm is None: 62 | return -1 63 | time = datetime.strptime(rm_tm, "%H:%M:%S") 64 | return time.hour * 60 + time.minute 65 | 66 | @property 67 | def water_heater_power_option_value(self) -> Optional[bool]: 68 | """Get water heater power option value""" 69 | return self.data.get(EvoDeviceProperties.PWR_OPT, None) 70 | 71 | @property 72 | def water_heater_maximum_setpoint_temperature_minimum(self) -> Optional[float]: 73 | """Get water heater maximum setpoint temperature minimum""" 74 | return self.plant_settings.get( 75 | MedDeviceSettings.MED_MAX_SETPOINT_TEMPERATURE_MIN, None 76 | ) 77 | 78 | @property 79 | def water_heater_maximum_setpoint_temperature_maximum(self) -> Optional[float]: 80 | """Get water heater maximum setpoint maximum temperature""" 81 | return self.plant_settings.get( 82 | MedDeviceSettings.MED_MAX_SETPOINT_TEMPERATURE_MAX, None 83 | ) 84 | 85 | def set_eco_mode(self, eco_mode: bool): 86 | """Set water heater eco_mode""" 87 | self.api.set_evo_eco_mode(self.gw, eco_mode) 88 | self.data[EvoDeviceProperties.ECO] = eco_mode 89 | 90 | async def async_set_eco_mode(self, eco_mode: bool): 91 | """Async set water heater eco_mode""" 92 | await self.api.async_set_evo_eco_mode(self.gw, eco_mode) 93 | self.data[EvoDeviceProperties.ECO] = eco_mode 94 | 95 | def set_water_heater_operation_mode(self, operation_mode: str): 96 | """Set water heater operation mode""" 97 | self.api.set_evo_mode(self.gw, EvoPlantMode[operation_mode]) 98 | self.data[EvoDeviceProperties.MODE] = EvoPlantMode[operation_mode].value 99 | 100 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 101 | """Async set water heater operation mode""" 102 | await self.api.async_set_evo_mode(self.gw, EvoPlantMode[operation_mode]) 103 | self.data[EvoDeviceProperties.MODE] = EvoPlantMode[operation_mode].value 104 | 105 | def set_water_heater_temperature(self, temperature: float): 106 | """Set water heater temperature""" 107 | self.api.set_evo_temperature(self.gw, temperature) 108 | self.data[EvoDeviceProperties.REQ_TEMP] = temperature 109 | 110 | async def async_set_water_heater_temperature(self, temperature: float): 111 | """Async set water heater temperature""" 112 | await self.api.async_set_evo_temperature(self.gw, temperature) 113 | self.data[EvoDeviceProperties.REQ_TEMP] = temperature 114 | -------------------------------------------------------------------------------- /ariston/evo_lydos_device.py: -------------------------------------------------------------------------------- 1 | """Evo and lydos device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from abc import ABC 6 | from typing import Optional 7 | 8 | from .velis_device import AristonVelisDevice 9 | from .const import EvoLydosDeviceProperties 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | class AristonEvoLydosDevice(AristonVelisDevice, ABC): 15 | """Class representing a physical device, it's state and properties.""" 16 | 17 | @property 18 | def water_heater_current_temperature(self) -> Optional[float]: 19 | """Get water heater current temperature""" 20 | return self.data.get(EvoLydosDeviceProperties.TEMP, None) 21 | 22 | @property 23 | def water_heater_target_temperature(self) -> Optional[float]: 24 | """Get water heater target temperature""" 25 | return self.data.get(EvoLydosDeviceProperties.REQ_TEMP, None) 26 | 27 | @property 28 | def av_shw_value(self) -> Optional[int]: 29 | """Get average showers value""" 30 | return self.data.get(EvoLydosDeviceProperties.AV_SHW, None) 31 | 32 | @property 33 | def is_heating(self) -> Optional[bool]: 34 | """Get is the water heater heating""" 35 | return self.data.get(EvoLydosDeviceProperties.HEAT_REQ, None) 36 | 37 | @property 38 | def is_antileg(self) -> Optional[bool]: 39 | """Is anti-legionella cycle running""" 40 | return self.data.get(EvoLydosDeviceProperties.ANTI_LEG, None) 41 | -------------------------------------------------------------------------------- /ariston/evo_one_device.py: -------------------------------------------------------------------------------- 1 | """Evo device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from datetime import datetime 6 | from typing import Optional 7 | 8 | from .const import ( 9 | VelisPlantMode, 10 | EvoOneDeviceProperties, 11 | PlantData, 12 | WaterHeaterMode, 13 | ) 14 | from .velis_base_device import AristonVelisBaseDevice 15 | 16 | _LOGGER = logging.getLogger(__name__) 17 | 18 | 19 | class AristonEvoOneDevice(AristonVelisBaseDevice): 20 | """Class representing a physical device, it's state and properties.""" 21 | 22 | @property 23 | def plant_data(self) -> PlantData: 24 | """Final string to get plant data""" 25 | return PlantData.PD 26 | 27 | @property 28 | def consumption_type(self) -> str: 29 | """String to get consumption type""" 30 | return "Dhw" 31 | 32 | @property 33 | def water_heater_mode(self) -> type[WaterHeaterMode]: 34 | """Return the water heater mode class""" 35 | return VelisPlantMode 36 | 37 | @property 38 | def water_heater_current_temperature(self) -> Optional[float]: 39 | """Get water heater current temperature""" 40 | return self.data.get(EvoOneDeviceProperties.TEMP, None) 41 | 42 | @property 43 | def av_shw_value(self) -> Optional[int]: 44 | """Get average showers value""" 45 | return self.data.get(EvoOneDeviceProperties.AV_SHW, None) 46 | 47 | @property 48 | def water_heater_eco_value(self) -> Optional[int]: 49 | """Get water heater eco value""" 50 | return self.data.get(EvoOneDeviceProperties.ECO, None) 51 | 52 | @property 53 | def rm_tm_value(self) -> Optional[str]: 54 | """Get remaining time value""" 55 | return self.data.get(EvoOneDeviceProperties.RM_TM, None) 56 | 57 | @property 58 | def rm_tm_in_minutes(self) -> int: 59 | """Get remaining time value in minutes""" 60 | rm_tm = self.rm_tm_value 61 | if rm_tm is None: 62 | return -1 63 | time = datetime.strptime(rm_tm, "%H:%M:%S") 64 | return time.hour * 60 + time.minute 65 | 66 | def set_eco_mode(self, eco_mode: bool): 67 | """Set water heater eco_mode""" 68 | self.api.set_evo_eco_mode(self.gw, eco_mode) 69 | self.data[EvoOneDeviceProperties.ECO] = eco_mode 70 | 71 | async def async_set_eco_mode(self, eco_mode: bool): 72 | """Async set water heater eco_mode""" 73 | await self.api.async_set_evo_eco_mode(self.gw, eco_mode) 74 | self.data[EvoOneDeviceProperties.ECO] = eco_mode 75 | 76 | def set_water_heater_operation_mode(self, operation_mode: str): 77 | """Set water heater operation mode""" 78 | self.api.set_evo_mode(self.gw, VelisPlantMode[operation_mode]) 79 | self.data[EvoOneDeviceProperties.MODE] = VelisPlantMode[operation_mode].value 80 | 81 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 82 | """Async set water heater operation mode""" 83 | await self.api.async_set_evo_mode(self.gw, VelisPlantMode[operation_mode]) 84 | self.data[EvoOneDeviceProperties.MODE] = VelisPlantMode[operation_mode].value 85 | 86 | @property 87 | def is_heating(self) -> Optional[bool]: 88 | """Get is the water heater heating""" 89 | return self.data.get(EvoOneDeviceProperties.STATE, None) 90 | 91 | @property 92 | def max_req_shower(self) -> Optional[str]: 93 | """Get maximum requestable shower""" 94 | return self.data.get(EvoOneDeviceProperties.MAX_REQ_SHW, None) 95 | 96 | @property 97 | def req_shower(self) -> Optional[str]: 98 | """Get requested shower""" 99 | return self.data.get(EvoOneDeviceProperties.REQ_SHW, None) 100 | 101 | def set_water_heater_number_of_showers(self, number_of_showers: int): 102 | """Set water heater number of showers""" 103 | self.api.set_evo_number_of_showers(self.gw, number_of_showers) 104 | self.data[EvoOneDeviceProperties.REQ_SHW] = number_of_showers 105 | 106 | async def async_set_water_heater_number_of_showers(self, number_of_showers: int): 107 | """Async set water heater number of showers""" 108 | await self.api.async_set_evo_number_of_showers(self.gw, number_of_showers) 109 | self.data[EvoOneDeviceProperties.REQ_SHW] = number_of_showers 110 | -------------------------------------------------------------------------------- /ariston/galevo_device.py: -------------------------------------------------------------------------------- 1 | """Galevo device class for Ariston module.""" 2 | 3 | from __future__ import annotations 4 | import asyncio 5 | 6 | import logging 7 | from datetime import date 8 | from typing import Any, Optional 9 | 10 | from .ariston_api import AristonAPI 11 | from .const import ( 12 | ConsumptionProperties, 13 | ConsumptionTimeInterval, 14 | ConsumptionType, 15 | Currency, 16 | CustomDeviceFeatures, 17 | DeviceFeatures, 18 | DeviceProperties, 19 | GasEnergyUnit, 20 | GasType, 21 | PlantMode, 22 | PropertyType, 23 | ThermostatProperties, 24 | ZoneAttribute, 25 | ZoneMode, 26 | MenuItemNames, 27 | ) 28 | from .device import AristonDevice 29 | 30 | _LOGGER = logging.getLogger(__name__) 31 | 32 | 33 | class AristonGalevoDevice(AristonDevice): 34 | """Class representing a physical device, it's state and properties.""" 35 | 36 | def __init__( 37 | self, 38 | api: AristonAPI, 39 | attributes: dict[str, Any], 40 | is_metric: bool = True, 41 | language_tag: str = "en-US", 42 | ) -> None: 43 | super().__init__(api, attributes) 44 | self.umsys = "si" if is_metric else "us" 45 | self.language_tag = language_tag 46 | self.consumptions_settings: dict[str, Any] = dict() 47 | self.energy_account: dict[str, Any] = dict() 48 | self.menu_items: list[dict[str, Any]] = list() 49 | 50 | @property 51 | def consumption_type(self) -> str: 52 | """String to get consumption type""" 53 | return f"Ch{'%2CDhw' if self.has_dhw else ''}{'%2CCooling' if self.hpmp_sys else ''}" 54 | 55 | @property 56 | def plant_mode_supported(self) -> bool: 57 | """Returns whether plant mode is supported""" 58 | return True 59 | 60 | def _update_state(self) -> None: 61 | """Set custom features""" 62 | if self.custom_features.get(CustomDeviceFeatures.HAS_OUTSIDE_TEMP) is None: 63 | temp = self._get_item_by_id( 64 | DeviceProperties.OUTSIDE_TEMP, PropertyType.VALUE 65 | ) 66 | max_temp = self._get_item_by_id( 67 | DeviceProperties.OUTSIDE_TEMP, PropertyType.MAX 68 | ) 69 | self.custom_features[CustomDeviceFeatures.HAS_OUTSIDE_TEMP] = ( 70 | temp != max_temp 71 | ) 72 | 73 | if self.custom_features.get(DeviceProperties.DHW_STORAGE_TEMPERATURE) is None: 74 | storage_temp = self._get_item_by_id( 75 | DeviceProperties.DHW_STORAGE_TEMPERATURE, PropertyType.VALUE 76 | ) 77 | max_storage_temp = self._get_item_by_id( 78 | DeviceProperties.DHW_STORAGE_TEMPERATURE, PropertyType.MAX 79 | ) 80 | self.custom_features[DeviceProperties.DHW_STORAGE_TEMPERATURE] = ( 81 | storage_temp is not None and storage_temp != max_storage_temp 82 | ) 83 | 84 | self.custom_features[DeviceProperties.CH_FLOW_TEMP] = ( 85 | self.ch_flow_temp_value is not None 86 | ) 87 | self.custom_features[DeviceProperties.IS_QUIET] = ( 88 | self.is_quiet_value is not None 89 | ) 90 | 91 | def update_state(self) -> None: 92 | """Update the device states from the cloud""" 93 | if len(self.features) == 0: 94 | self.get_features() 95 | 96 | self.data = self.api.get_properties( 97 | self.gw, 98 | self.features, 99 | self.language_tag, 100 | self.umsys, 101 | ) 102 | self.menu_items = self.api.get_menu_items(self.gw) 103 | self._update_state() 104 | 105 | async def async_update_state(self) -> None: 106 | """Async update the device states from the cloud""" 107 | if len(self.features) == 0: 108 | await self.async_get_features() 109 | 110 | (self.data, self.menu_items) = await asyncio.gather( 111 | self.api.async_get_properties( 112 | self.gw, 113 | self.features, 114 | self.language_tag, 115 | self.umsys, 116 | ), 117 | self.api.async_get_menu_items(self.gw), 118 | ) 119 | self._update_state() 120 | 121 | def _get_features(self) -> None: 122 | """Set custom features""" 123 | self.custom_features[CustomDeviceFeatures.HAS_DHW] = ( 124 | self.features.get(DeviceFeatures.HAS_BOILER, False) 125 | or self.features.get(DeviceFeatures.DHW_BOILER_PRESENT, False) 126 | or self.features.get(DeviceFeatures.DHW_MODE_CHANGEABLE, False) 127 | or self.features.get(DeviceFeatures.DHW_PROG_SUPPORTED, False) 128 | ) 129 | 130 | def get_features(self) -> None: 131 | """Get device features wrapper""" 132 | super().get_features() 133 | self._get_features() 134 | 135 | async def async_get_features(self) -> None: 136 | """Async get device features wrapper""" 137 | await super().async_get_features() 138 | self._get_features() 139 | 140 | @property 141 | def zones(self) -> list[dict[str, Any]]: 142 | """Get device zones wrapper""" 143 | return self.features.get(DeviceFeatures.ZONES, list[dict[str, Any]]) 144 | 145 | @property 146 | def zone_numbers(self) -> list[int]: 147 | """Get zone number for device""" 148 | return [zone.get(ZoneAttribute.NUM, 0) for zone in self.zones] 149 | 150 | @property 151 | def water_heater_current_temperature(self) -> Optional[float]: 152 | """Get water heater current temperature""" 153 | if self.custom_features.get(DeviceProperties.DHW_STORAGE_TEMPERATURE): 154 | return self._get_item_by_id( 155 | DeviceProperties.DHW_STORAGE_TEMPERATURE, PropertyType.VALUE 156 | ) 157 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.VALUE) 158 | 159 | @property 160 | def water_heater_minimum_temperature(self) -> float: 161 | """Get water heater minimum temperature""" 162 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.MIN) 163 | 164 | @property 165 | def water_heater_maximum_temperature(self) -> Optional[float]: 166 | """Get water heater maximum temperature""" 167 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.MAX) 168 | 169 | @property 170 | def water_heater_target_temperature(self) -> Optional[float]: 171 | """Get water heater target temperature""" 172 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.VALUE) 173 | 174 | @property 175 | def water_heater_temperature_decimals(self) -> int: 176 | """Get water heater temperature decimals""" 177 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.DECIMALS) 178 | 179 | @property 180 | def water_heater_temperature_unit(self) -> str: 181 | """Get water heater temperature unit""" 182 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.UNIT) 183 | 184 | @property 185 | def water_heater_temperature_step(self) -> int: 186 | """Get water heater temperature step""" 187 | return self._get_item_by_id(DeviceProperties.DHW_TEMP, PropertyType.STEP) 188 | 189 | @property 190 | def water_heater_mode_operation_texts(self) -> list[str]: 191 | """Get water heater operation mode texts""" 192 | return self._get_item_by_id(DeviceProperties.DHW_MODE, PropertyType.OPT_TEXTS) 193 | 194 | @property 195 | def water_heater_mode_options(self) -> list[int]: 196 | """Get water heater operation options""" 197 | return self._get_item_by_id(DeviceProperties.DHW_MODE, PropertyType.OPTIONS) 198 | 199 | @property 200 | def water_heater_mode_value(self) -> Optional[int]: 201 | """Get water heater mode value""" 202 | return self._get_item_by_id(DeviceProperties.DHW_MODE, PropertyType.VALUE) 203 | 204 | def get_zone_heat_request_value(self, zone_number: int) -> str: 205 | """Get zone heat request value""" 206 | return self._get_item_by_id( 207 | ThermostatProperties.ZONE_HEAT_REQUEST, PropertyType.VALUE, zone_number 208 | ) 209 | 210 | def get_zone_economy_temp_value(self, zone_number: int) -> str: 211 | """Get zone economy temperature value""" 212 | return self._get_item_by_id( 213 | ThermostatProperties.ZONE_ECONOMY_TEMP, PropertyType.VALUE, zone_number 214 | ) 215 | 216 | @property 217 | def is_plant_in_heat_mode(self) -> bool: 218 | """Is the plant in a heat mode""" 219 | return self.plant_mode in [ 220 | PlantMode.WINTER, 221 | PlantMode.HEATING_ONLY, 222 | ] 223 | 224 | @property 225 | def is_plant_in_cool_mode(self) -> bool: 226 | """Is the plant in a cool mode""" 227 | return self.plant_mode in [ 228 | PlantMode.COOLING, 229 | PlantMode.COOLING_ONLY, 230 | ] 231 | 232 | def is_zone_in_manual_mode(self, zone: int) -> bool: 233 | """Is zone in manual mode""" 234 | return self.get_zone_mode(zone) in [ 235 | ZoneMode.MANUAL, 236 | ZoneMode.MANUAL_NIGHT, 237 | ] 238 | 239 | def is_zone_in_time_program_mode(self, zone: int) -> bool: 240 | """Is zone in time program mode""" 241 | return self.get_zone_mode(zone) in [ 242 | ZoneMode.TIME_PROGRAM, 243 | ] 244 | 245 | def is_zone_mode_options_contains_manual(self, zone: int) -> bool: 246 | """Is zone mode options contains manual mode""" 247 | return ZoneMode.MANUAL.value in self.get_zone_mode_options( 248 | zone 249 | ) or ZoneMode.MANUAL_NIGHT.value in self.get_zone_mode_options(zone) 250 | 251 | def is_zone_mode_options_contains_time_program(self, zone: int) -> bool: 252 | """Is zone mode options contains time program mode""" 253 | return ZoneMode.TIME_PROGRAM.value in self.get_zone_mode_options(zone) 254 | 255 | def is_zone_mode_options_contains_off(self, zone: int) -> bool: 256 | """Is zone mode options contains off mode""" 257 | return ZoneMode.OFF.value in self.get_zone_mode_options(zone) 258 | 259 | @property 260 | def is_plant_mode_options_contains_off(self) -> bool: 261 | """Is plant mode options contains off mode""" 262 | return PlantMode.OFF.value in self.plant_mode_options 263 | 264 | @property 265 | def is_plant_mode_options_contains_cooling(self) -> bool: 266 | """Is plant mode options contains cooling mode""" 267 | return ( 268 | PlantMode.COOLING.value in self.plant_mode_options 269 | or PlantMode.COOLING_ONLY.value in self.plant_mode_options 270 | ) 271 | 272 | @property 273 | def holiday_expires_on(self) -> str: 274 | """Get holiday expires on""" 275 | return self._get_item_by_id(DeviceProperties.HOLIDAY, PropertyType.EXPIRES_ON) 276 | 277 | @property 278 | def automatic_thermoregulation(self) -> str: 279 | """Get automatic thermoregulation""" 280 | return self._get_item_by_id( 281 | DeviceProperties.AUTOMATIC_THERMOREGULATION, PropertyType.VALUE 282 | ) 283 | 284 | @property 285 | def heating_circuit_pressure_value(self) -> str: 286 | """Get heating circuit pressure value""" 287 | return self._get_item_by_id( 288 | DeviceProperties.HEATING_CIRCUIT_PRESSURE, PropertyType.VALUE 289 | ) 290 | 291 | @property 292 | def heating_circuit_pressure_unit(self) -> str: 293 | """Get heating circuit pressure unit""" 294 | return self._get_item_by_id( 295 | DeviceProperties.HEATING_CIRCUIT_PRESSURE, PropertyType.UNIT 296 | ) 297 | 298 | @property 299 | def hybrid_mode(self) -> str: 300 | """Get hybrid mode value""" 301 | return self.hybrid_mode_opt_texts[ 302 | self.hybrid_mode_options.index(self.hybrid_mode_value) 303 | ] 304 | 305 | @property 306 | def hybrid_mode_value(self) -> str: 307 | """Get hybrid mode value""" 308 | return self._get_item_by_id(DeviceProperties.HYBRID_MODE, PropertyType.VALUE) 309 | 310 | @property 311 | def hybrid_mode_options(self) -> str: 312 | """Get hybrid mode options""" 313 | return self._get_item_by_id(DeviceProperties.HYBRID_MODE, PropertyType.OPTIONS) 314 | 315 | @property 316 | def hybrid_mode_opt_texts(self) -> str: 317 | """Get hybrid mode opt texts""" 318 | return self._get_item_by_id( 319 | DeviceProperties.HYBRID_MODE, PropertyType.OPT_TEXTS 320 | ) 321 | 322 | @property 323 | def buffer_control_mode(self) -> str: 324 | """Get buffer control mode""" 325 | return self.buffer_control_mode_opt_texts[ 326 | self.buffer_control_mode_options.index(self.buffer_control_mode_value) 327 | ] 328 | 329 | @property 330 | def buffer_control_mode_value(self) -> str: 331 | """Get buffer control mode value""" 332 | return self._get_item_by_id( 333 | DeviceProperties.BUFFER_CONTROL_MODE, PropertyType.VALUE 334 | ) 335 | 336 | @property 337 | def buffer_control_mode_options(self) -> str: 338 | """Get buffer control mode options""" 339 | return self._get_item_by_id( 340 | DeviceProperties.BUFFER_CONTROL_MODE, PropertyType.OPTIONS 341 | ) 342 | 343 | @property 344 | def buffer_control_mode_opt_texts(self) -> str: 345 | """Get buffer control mode opt texts""" 346 | return self._get_item_by_id( 347 | DeviceProperties.BUFFER_CONTROL_MODE, PropertyType.OPT_TEXTS 348 | ) 349 | 350 | @property 351 | def is_quiet_value(self) -> Optional[str]: 352 | """Get is quiet value""" 353 | return self._get_item_by_id(DeviceProperties.IS_QUIET, PropertyType.VALUE) 354 | 355 | @property 356 | def ch_flow_setpoint_temp_value(self) -> str: 357 | """Get central heating flow setpoint temperature value""" 358 | return self._get_item_by_id( 359 | DeviceProperties.CH_FLOW_SETPOINT_TEMP, PropertyType.VALUE 360 | ) 361 | 362 | @property 363 | def ch_flow_temp_value(self) -> Optional[str]: 364 | """Get central heating flow temperature value""" 365 | return self._get_item_by_id(DeviceProperties.CH_FLOW_TEMP, PropertyType.VALUE) 366 | 367 | @property 368 | def outside_temp_value(self) -> str: 369 | """Get outside temperature value""" 370 | return self._get_item_by_id(DeviceProperties.OUTSIDE_TEMP, PropertyType.VALUE) 371 | 372 | @property 373 | def outside_temp_unit(self) -> str: 374 | """Get outside temperature unit""" 375 | return self._get_item_by_id(DeviceProperties.OUTSIDE_TEMP, PropertyType.UNIT) 376 | 377 | @property 378 | def ch_flow_setpoint_temp_unit(self) -> str: 379 | """Get central heating flow setpoint temperature unit""" 380 | return self._get_item_by_id( 381 | DeviceProperties.CH_FLOW_SETPOINT_TEMP, PropertyType.UNIT 382 | ) 383 | 384 | @property 385 | def ch_flow_temp_unit(self) -> str: 386 | """Get central heating flow temperature unit""" 387 | return self._get_item_by_id(DeviceProperties.CH_FLOW_TEMP, PropertyType.UNIT) 388 | 389 | @property 390 | def is_flame_on_value(self) -> bool: 391 | """Get is flame on value""" 392 | return self._get_item_by_id(DeviceProperties.IS_FLAME_ON, PropertyType.VALUE) 393 | 394 | @property 395 | def is_heating_pump_on_value(self) -> bool: 396 | """Get is heating pump on value""" 397 | return self._get_item_by_id( 398 | DeviceProperties.IS_HEATING_PUMP_ON, PropertyType.VALUE 399 | ) 400 | 401 | @property 402 | def holiday_mode_value(self) -> bool: 403 | """Get holiday mode on value""" 404 | return self._get_item_by_id(DeviceProperties.HOLIDAY, PropertyType.VALUE) 405 | 406 | def get_zone_mode(self, zone: int) -> ZoneMode: 407 | """Get zone mode on value""" 408 | return ZoneMode( 409 | self._get_item_by_id( 410 | ThermostatProperties.ZONE_MODE, 411 | PropertyType.VALUE, 412 | zone, 413 | ZoneMode.UNDEFINED, 414 | ) 415 | ) 416 | 417 | def get_zone_mode_options(self, zone: int) -> list[int]: 418 | """Get zone mode on options""" 419 | return self._get_item_by_id( 420 | ThermostatProperties.ZONE_MODE, PropertyType.OPTIONS, zone 421 | ) 422 | 423 | @property 424 | def plant_mode(self) -> PlantMode: 425 | """Get plant mode value""" 426 | return PlantMode( 427 | self._get_item_by_id( 428 | DeviceProperties.PLANT_MODE, 429 | PropertyType.VALUE, 430 | default=PlantMode.UNDEFINED, 431 | ) 432 | ) 433 | 434 | @property 435 | def plant_mode_options(self) -> list[int]: 436 | """Get plant mode on options""" 437 | return self._get_item_by_id(DeviceProperties.PLANT_MODE, PropertyType.OPTIONS) 438 | 439 | @property 440 | def plant_mode_opt_texts(self) -> list[str]: 441 | """Get plant mode on option texts""" 442 | return self._get_item_by_id(DeviceProperties.PLANT_MODE, PropertyType.OPT_TEXTS) 443 | 444 | @property 445 | def plant_mode_text(self) -> str: 446 | """Get plant mode on option texts""" 447 | current_plant_mode = self.plant_mode.value 448 | plant_mode_options = self.plant_mode_options 449 | if current_plant_mode in plant_mode_options: 450 | index = plant_mode_options.index(current_plant_mode) 451 | return self._get_item_by_id( 452 | DeviceProperties.PLANT_MODE, PropertyType.OPT_TEXTS 453 | )[index] 454 | return PlantMode.UNDEFINED.name 455 | 456 | @property 457 | def signal_strength_value(self) -> Any: 458 | """Get signal strength value""" 459 | return self._get_menu_item_by_id( 460 | MenuItemNames.SIGNAL_STRENGTH.value, PropertyType.VALUE 461 | ) 462 | 463 | @property 464 | def signal_strength_unit(self) -> Any: 465 | """Get signal strength unit""" 466 | return self._get_menu_item_by_id( 467 | MenuItemNames.SIGNAL_STRENGTH.value, PropertyType.UNIT 468 | ) 469 | 470 | @property 471 | def ch_return_temp_value(self) -> Any: 472 | """Get ch return temp value""" 473 | return self._get_menu_item_by_id( 474 | MenuItemNames.CH_RETURN_TEMP.value, PropertyType.VALUE 475 | ) 476 | 477 | @property 478 | def ch_return_temp_unit(self) -> Any: 479 | """Get ch return temp unit""" 480 | return self._get_menu_item_by_id( 481 | MenuItemNames.CH_RETURN_TEMP.value, PropertyType.UNIT 482 | ) 483 | 484 | def get_measured_temp_unit(self, zone: int) -> str: 485 | """Get zone measured temp unit""" 486 | return self._get_item_by_id( 487 | ThermostatProperties.ZONE_MEASURED_TEMP, PropertyType.UNIT, zone 488 | ) 489 | 490 | def get_measured_temp_decimals(self, zone: int) -> int: 491 | """Get zone measured temp decimals""" 492 | return self._get_item_by_id( 493 | ThermostatProperties.ZONE_MEASURED_TEMP, PropertyType.DECIMALS, zone 494 | ) 495 | 496 | def get_measured_temp_value(self, zone: int) -> int: 497 | """Get zone measured temp value""" 498 | return self._get_item_by_id( 499 | ThermostatProperties.ZONE_MEASURED_TEMP, PropertyType.VALUE, zone 500 | ) 501 | 502 | def get_comfort_temp_min(self, zone: int) -> int: 503 | """Get zone comfort temp min""" 504 | return self._get_item_by_id( 505 | ThermostatProperties.ZONE_COMFORT_TEMP, PropertyType.MIN, zone 506 | ) 507 | 508 | def get_comfort_temp_max(self, zone: int) -> int: 509 | """Get zone comfort temp max""" 510 | return self._get_item_by_id( 511 | ThermostatProperties.ZONE_COMFORT_TEMP, PropertyType.MAX, zone 512 | ) 513 | 514 | def get_comfort_temp_step(self, zone: int) -> int: 515 | """Get zone comfort temp step""" 516 | return self._get_item_by_id( 517 | ThermostatProperties.ZONE_COMFORT_TEMP, PropertyType.STEP, zone 518 | ) 519 | 520 | def get_comfort_temp_value(self, zone: int) -> int: 521 | """Get zone comfort temp value""" 522 | return self._get_item_by_id( 523 | ThermostatProperties.ZONE_COMFORT_TEMP, PropertyType.VALUE, zone 524 | ) 525 | 526 | def get_target_temp_step(self, zone: int) -> int: 527 | """Get zone target temp step""" 528 | return self._get_item_by_id( 529 | ThermostatProperties.ZONE_DESIRED_TEMP, PropertyType.STEP, zone 530 | ) 531 | 532 | def get_target_temp_value(self, zone: int) -> int: 533 | """Get zone target temp value""" 534 | return self._get_item_by_id( 535 | ThermostatProperties.ZONE_DESIRED_TEMP, PropertyType.VALUE, zone 536 | ) 537 | 538 | def get_heating_flow_offset_value(self, zone: int) -> int: 539 | """Get zone heating flow offset value""" 540 | return self._get_item_by_id( 541 | ThermostatProperties.HEATING_FLOW_OFFSET, PropertyType.VALUE, zone 542 | ) 543 | 544 | def get_heating_flow_offset_unit(self, zone: int) -> int: 545 | """Get zone heating flow offset unit""" 546 | return self._get_item_by_id( 547 | ThermostatProperties.HEATING_FLOW_OFFSET, PropertyType.UNIT, zone 548 | ) 549 | 550 | def get_heating_flow_offset_step(self, zone: int) -> int: 551 | """Get zone heating flow offset step""" 552 | return self._get_item_by_id( 553 | ThermostatProperties.HEATING_FLOW_OFFSET, PropertyType.STEP, zone 554 | ) 555 | 556 | def get_heating_flow_offset_max(self, zone: int) -> int: 557 | """Get zone heating flow offset max""" 558 | return self._get_item_by_id( 559 | ThermostatProperties.HEATING_FLOW_OFFSET, PropertyType.MAX, zone 560 | ) 561 | 562 | def get_heating_flow_offset_min(self, zone: int) -> int: 563 | """Get zone heating flow offset min""" 564 | return self._get_item_by_id( 565 | ThermostatProperties.HEATING_FLOW_OFFSET, PropertyType.MIN, zone 566 | ) 567 | 568 | def get_heating_flow_offset_decimals(self, zone: int) -> int: 569 | """Get zone heating flow offset decimals""" 570 | return self._get_item_by_id( 571 | ThermostatProperties.HEATING_FLOW_OFFSET, PropertyType.DECIMALS, zone 572 | ) 573 | 574 | def get_heating_flow_temp_value(self, zone: int) -> int: 575 | """Get zone heating flow temp value""" 576 | return self._get_item_by_id( 577 | ThermostatProperties.HEATING_FLOW_TEMP, PropertyType.VALUE, zone 578 | ) 579 | 580 | def get_heating_flow_temp_unit(self, zone: int) -> int: 581 | """Get zone heating flow temp unit""" 582 | return self._get_item_by_id( 583 | ThermostatProperties.HEATING_FLOW_TEMP, PropertyType.UNIT, zone 584 | ) 585 | 586 | def get_heating_flow_temp_step(self, zone: int) -> int: 587 | """Get zone heating flow temp step""" 588 | return self._get_item_by_id( 589 | ThermostatProperties.HEATING_FLOW_TEMP, PropertyType.STEP, zone 590 | ) 591 | 592 | def get_heating_flow_temp_max(self, zone: int) -> int: 593 | """Get zone heating flow temp max""" 594 | return self._get_item_by_id( 595 | ThermostatProperties.HEATING_FLOW_TEMP, PropertyType.MAX, zone 596 | ) 597 | 598 | def get_heating_flow_temp_min(self, zone: int) -> int: 599 | """Get zone heating flow temp min""" 600 | return self._get_item_by_id( 601 | ThermostatProperties.HEATING_FLOW_TEMP, PropertyType.MIN, zone 602 | ) 603 | 604 | def get_heating_flow_temp_decimals(self, zone: int) -> int: 605 | """Get zone heating flow temp decimals""" 606 | return self._get_item_by_id( 607 | ThermostatProperties.HEATING_FLOW_TEMP, PropertyType.DECIMALS, zone 608 | ) 609 | 610 | def _get_item_by_id( 611 | self, item_id: str, item_value: str, zone_number: int = 0, default: Any = None 612 | ) -> Any: 613 | """Get item attribute from data""" 614 | return next( 615 | ( 616 | item.get(item_value) 617 | for item in self.data.get("items", []) 618 | if item.get("id") == item_id 619 | and item.get(PropertyType.ZONE) == zone_number 620 | ), 621 | default, 622 | ) 623 | 624 | def _get_menu_item_by_id(self, menu_item_id: int, item_value: str) -> Any: 625 | """Get item attribute from data""" 626 | return next( 627 | ( 628 | item.get(item_value) 629 | for item in self.menu_items 630 | if item.get("id") == menu_item_id 631 | ), 632 | None, 633 | ) 634 | 635 | @property 636 | def elect_cost(self) -> Optional[float]: 637 | """Get electric consumption cost""" 638 | return self.consumptions_settings.get(ConsumptionProperties.ELEC_COST, None) 639 | 640 | @property 641 | def gas_cost(self) -> Optional[float]: 642 | """Get gas consumption cost""" 643 | return self.consumptions_settings.get(ConsumptionProperties.GAS_COST, None) 644 | 645 | @property 646 | def gas_type(self) -> Optional[str]: 647 | """Get gas type""" 648 | gas_type = self.consumptions_settings.get(ConsumptionProperties.GAS_TYPE, None) 649 | if gas_type in list(GasType): 650 | return GasType(gas_type).name 651 | return None 652 | 653 | @staticmethod 654 | def get_gas_types() -> list[str]: 655 | """Get all gas types""" 656 | return [c.name for c in GasType] 657 | 658 | def set_gas_type(self, selected: str): 659 | """Set gas type""" 660 | self._set_consumptions_settings( 661 | ConsumptionProperties.GAS_TYPE, GasType[selected].value 662 | ) 663 | 664 | async def async_set_gas_type(self, selected: str): 665 | """Async set gas type""" 666 | await self._async_set_consumptions_settings( 667 | ConsumptionProperties.GAS_TYPE, GasType[selected].value 668 | ) 669 | 670 | @property 671 | def currency(self) -> Optional[str]: 672 | """Get gas type""" 673 | currency = self.consumptions_settings.get(ConsumptionProperties.CURRENCY, None) 674 | if currency in list(Currency): 675 | return Currency(currency).name 676 | return None 677 | 678 | @staticmethod 679 | def get_currencies() -> list[str]: 680 | """Get all currency""" 681 | return [c.name for c in Currency] 682 | 683 | def set_currency(self, selected: str): 684 | """Set currency""" 685 | self._set_consumptions_settings( 686 | ConsumptionProperties.CURRENCY, Currency[selected].value 687 | ) 688 | 689 | async def async_set_currency(self, selected: str): 690 | """Async set currency""" 691 | await self._async_set_consumptions_settings( 692 | ConsumptionProperties.CURRENCY, Currency[selected].value 693 | ) 694 | 695 | @property 696 | def gas_energy_unit(self) -> Optional[str]: 697 | """Get gas energy unit""" 698 | gas_energy_unit = self.consumptions_settings.get( 699 | ConsumptionProperties.GAS_ENERGY_UNIT, None 700 | ) 701 | if gas_energy_unit in list(GasEnergyUnit): 702 | return GasEnergyUnit(gas_energy_unit).name 703 | return None 704 | 705 | @staticmethod 706 | def get_gas_energy_units() -> list[str]: 707 | """Get all gas energy unit""" 708 | return [c.name for c in GasEnergyUnit] 709 | 710 | def set_gas_energy_unit(self, selected: str): 711 | """Set gas energy unit""" 712 | self._set_consumptions_settings( 713 | ConsumptionProperties.GAS_ENERGY_UNIT, GasEnergyUnit[selected].value 714 | ) 715 | 716 | async def async_set_gas_energy_unit(self, selected: str): 717 | """Async set gas energy unit""" 718 | await self._async_set_consumptions_settings( 719 | ConsumptionProperties.GAS_ENERGY_UNIT, GasEnergyUnit[selected].value 720 | ) 721 | 722 | @property 723 | def gas_consumption_for_heating_last_month(self) -> Optional[int]: 724 | """Get gas consumption for heatig last month""" 725 | energy_account_last_month = self.energy_account.get("LastMonth", None) 726 | if not energy_account_last_month: 727 | return None 728 | return energy_account_last_month[0].get("gas", None) 729 | 730 | @property 731 | def electricity_consumption_for_heating_last_month(self) -> Optional[int]: 732 | """Get electricity consumption for heating last month""" 733 | energy_account_last_month = self.energy_account.get("LastMonth", None) 734 | if not energy_account_last_month: 735 | return None 736 | return energy_account_last_month[0].get("elect", None) 737 | 738 | @property 739 | def electricity_consumption_for_cooling_last_month(self) -> Optional[int]: 740 | """Get electricity consumption for cooling last month""" 741 | energy_account_last_month = self.energy_account.get("LastMonth", None) 742 | if not energy_account_last_month: 743 | return None 744 | return energy_account_last_month[0].get("cool", None) 745 | 746 | @property 747 | def gas_consumption_for_water_last_month(self) -> Optional[int]: 748 | """Get gas consumption for water last month""" 749 | energy_account_last_month = self.energy_account.get("LastMonth", None) 750 | if not energy_account_last_month or len(energy_account_last_month) < 2: 751 | return None 752 | return energy_account_last_month[1].get("gas", None) 753 | 754 | @property 755 | def electricity_consumption_for_water_last_month(self) -> Optional[int]: 756 | """Get electricity consumption for water last month""" 757 | energy_account_last_month = self.energy_account.get("LastMonth", None) 758 | if not energy_account_last_month or len(energy_account_last_month) < 2: 759 | return None 760 | return energy_account_last_month[1].get("elect", None) 761 | 762 | def set_elect_cost(self, value: float): 763 | """Set electric cost""" 764 | self._set_consumptions_settings(ConsumptionProperties.ELEC_COST, value) 765 | 766 | async def async_set_elect_cost(self, value: float): 767 | """Async set electric cost""" 768 | await self._async_set_consumptions_settings( 769 | ConsumptionProperties.ELEC_COST, value 770 | ) 771 | 772 | def set_gas_cost(self, value: float): 773 | """Set gas cost""" 774 | self._set_consumptions_settings(ConsumptionProperties.GAS_COST, value) 775 | 776 | async def async_set_gas_cost(self, value: float): 777 | """Async set gas cost""" 778 | await self._async_set_consumptions_settings( 779 | ConsumptionProperties.GAS_COST, value 780 | ) 781 | 782 | def _set_consumptions_settings(self, consumption_property: str, value: float): 783 | """Set consumption settings""" 784 | new_settings = self.consumptions_settings.copy() 785 | new_settings[consumption_property] = value 786 | self.api.set_consumptions_settings(self.gw, new_settings) 787 | self.consumptions_settings[consumption_property] = value 788 | 789 | async def _async_set_consumptions_settings( 790 | self, consumption_property: str, value: float 791 | ): 792 | """Async set consumption settings""" 793 | new_settings = self.consumptions_settings.copy() 794 | new_settings[consumption_property] = value 795 | await self.api.async_set_consumptions_settings(self.gw, new_settings) 796 | self.consumptions_settings[consumption_property] = value 797 | 798 | def set_water_heater_temperature(self, temperature: float): 799 | """Set water heater temperature""" 800 | self.set_item_by_id(DeviceProperties.DHW_TEMP, temperature) 801 | 802 | async def async_set_water_heater_temperature(self, temperature: float): 803 | """Async set water heater temperature""" 804 | await self.async_set_item_by_id(DeviceProperties.DHW_TEMP, temperature) 805 | 806 | def set_water_heater_operation_mode(self, operation_mode: str): 807 | """Set water heater operation mode""" 808 | self.set_item_by_id( 809 | DeviceProperties.DHW_MODE, 810 | self._get_item_by_id( 811 | DeviceProperties.DHW_MODE, PropertyType.OPT_TEXTS 812 | ).index(operation_mode), 813 | ) 814 | 815 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 816 | """Async set water heater operation mode""" 817 | await self.async_set_item_by_id( 818 | DeviceProperties.DHW_MODE, 819 | self._get_item_by_id( 820 | DeviceProperties.DHW_MODE, PropertyType.OPT_TEXTS 821 | ).index(operation_mode), 822 | ) 823 | 824 | def set_automatic_thermoregulation(self, auto_thermo: bool): 825 | """Set automatic thermoregulation""" 826 | self.set_item_by_id( 827 | DeviceProperties.AUTOMATIC_THERMOREGULATION, 1.0 if auto_thermo else 0.0 828 | ) 829 | 830 | async def async_set_automatic_thermoregulation(self, auto_thermo: bool): 831 | """Async set automatic thermoregulation""" 832 | await self.async_set_item_by_id( 833 | DeviceProperties.AUTOMATIC_THERMOREGULATION, 1.0 if auto_thermo else 0.0 834 | ) 835 | 836 | def set_hybrid_mode(self, hybrid_mode: str): 837 | """Set hybrid mode""" 838 | self.set_item_by_id( 839 | DeviceProperties.HYBRID_MODE, 840 | self._get_item_by_id( 841 | DeviceProperties.HYBRID_MODE, PropertyType.OPT_TEXTS 842 | ).index(hybrid_mode), 843 | ) 844 | 845 | async def async_set_hybrid_mode(self, hybrid_mode: str): 846 | """Async set hybrid mode""" 847 | await self.async_set_item_by_id( 848 | DeviceProperties.HYBRID_MODE, 849 | self._get_item_by_id( 850 | DeviceProperties.HYBRID_MODE, PropertyType.OPT_TEXTS 851 | ).index(hybrid_mode), 852 | ) 853 | 854 | def set_buffer_control_mode(self, buffer_control_mode: str): 855 | """Set buffer control mode""" 856 | self.set_item_by_id( 857 | DeviceProperties.BUFFER_CONTROL_MODE, 858 | self._get_item_by_id( 859 | DeviceProperties.BUFFER_CONTROL_MODE, PropertyType.OPT_TEXTS 860 | ).index(buffer_control_mode), 861 | ) 862 | 863 | async def async_set_buffer_control_mode(self, buffer_control_mode: str): 864 | """Async set buffer control mode""" 865 | await self.async_set_item_by_id( 866 | DeviceProperties.BUFFER_CONTROL_MODE, 867 | self._get_item_by_id( 868 | DeviceProperties.BUFFER_CONTROL_MODE, PropertyType.OPT_TEXTS 869 | ).index(buffer_control_mode), 870 | ) 871 | 872 | def set_is_quiet(self, is_quiet: bool): 873 | """Set is quiet""" 874 | self.set_item_by_id(DeviceProperties.IS_QUIET, 1.0 if is_quiet else 0.0) 875 | 876 | async def async_set_is_quiet(self, is_quiet: bool): 877 | """Async set is quiet""" 878 | await self.async_set_item_by_id( 879 | DeviceProperties.IS_QUIET, 1.0 if is_quiet else 0.0 880 | ) 881 | 882 | def set_plant_mode(self, plant_mode: PlantMode): 883 | """Set plant mode""" 884 | self.set_item_by_id(DeviceProperties.PLANT_MODE, plant_mode.value) 885 | 886 | async def async_set_plant_mode(self, plant_mode: PlantMode): 887 | """Async set plant mode""" 888 | await self.async_set_item_by_id(DeviceProperties.PLANT_MODE, plant_mode.value) 889 | 890 | def set_zone_mode(self, zone_mode: ZoneMode, zone: int): 891 | """Set zone mode""" 892 | self.set_item_by_id(ThermostatProperties.ZONE_MODE, zone_mode.value, zone) 893 | 894 | async def async_set_zone_mode(self, zone_mode: ZoneMode, zone: int): 895 | """Async set zone mode""" 896 | await self.async_set_item_by_id( 897 | ThermostatProperties.ZONE_MODE, zone_mode.value, zone 898 | ) 899 | 900 | def set_comfort_temp(self, temp: float, zone: int): 901 | """Set comfort temp""" 902 | self.set_item_by_id(ThermostatProperties.ZONE_COMFORT_TEMP, temp, zone) 903 | 904 | async def async_set_comfort_temp(self, temp: float, zone: int): 905 | """Async set comfort temp""" 906 | await self.async_set_item_by_id( 907 | ThermostatProperties.ZONE_COMFORT_TEMP, temp, zone 908 | ) 909 | 910 | def set_heating_flow_temp(self, temp: float, zone: int): 911 | """Set heating flow temp""" 912 | self.set_item_by_id(ThermostatProperties.HEATING_FLOW_TEMP, temp, zone) 913 | 914 | async def async_set_heating_flow_temp(self, temp: float, zone: int): 915 | """Async set heating flow temp""" 916 | await self.async_set_item_by_id( 917 | ThermostatProperties.HEATING_FLOW_TEMP, temp, zone 918 | ) 919 | 920 | def set_heating_flow_offset(self, offset: float, zone: int): 921 | """Set heating flow offset""" 922 | self.set_item_by_id(ThermostatProperties.HEATING_FLOW_OFFSET, offset, zone) 923 | 924 | async def async_set_heating_flow_offset(self, offset: float, zone: int): 925 | """Async set heating flow offset""" 926 | await self.async_set_item_by_id( 927 | ThermostatProperties.HEATING_FLOW_OFFSET, offset, zone 928 | ) 929 | 930 | def _set_item_by_id( 931 | self, 932 | item_id: str, 933 | value: float, 934 | zone_number: int = 0, 935 | ): 936 | for item in self.data.get("items", list[dict[str, Any]]()): 937 | if item.get("id") == item_id and item.get(PropertyType.ZONE) == zone_number: 938 | item[PropertyType.VALUE] = value 939 | break 940 | 941 | def set_item_by_id( 942 | self, 943 | item_id: str, 944 | value: float, 945 | zone_number: int = 0, 946 | ): 947 | """Set item attribute on device""" 948 | current_value = self._get_item_by_id(item_id, PropertyType.VALUE, zone_number) 949 | self.api.set_property( 950 | self.gw, 951 | zone_number, 952 | self.features, 953 | item_id, 954 | value, 955 | current_value, 956 | self.umsys, 957 | ) 958 | self._set_item_by_id(item_id, value, zone_number) 959 | 960 | async def async_set_item_by_id( 961 | self, 962 | item_id: str, 963 | value: float, 964 | zone_number: int = 0, 965 | ): 966 | """Async set item attribute on device""" 967 | current_value = self._get_item_by_id(item_id, PropertyType.VALUE, zone_number) 968 | await self.api.async_set_property( 969 | self.gw, 970 | zone_number, 971 | self.features, 972 | item_id, 973 | value, 974 | current_value, 975 | self.umsys, 976 | ) 977 | self._set_item_by_id(item_id, value, zone_number) 978 | 979 | @staticmethod 980 | def _create_holiday_end_date(holiday_end: Optional[date]): 981 | return ( 982 | None if holiday_end is None else holiday_end.strftime("%Y-%m-%dT00:00:00") 983 | ) 984 | 985 | def _set_holiday(self, holiday_end_date: Optional[str]): 986 | for item in self.data.get("items", list[dict[str, Any]]()): 987 | if item.get("id") == DeviceProperties.HOLIDAY: 988 | item[PropertyType.VALUE] = False if holiday_end_date is None else True 989 | item[PropertyType.EXPIRES_ON] = ( 990 | None if holiday_end_date is None else holiday_end_date 991 | ) 992 | break 993 | 994 | def set_holiday(self, holiday_end: date): 995 | """Set holiday on device""" 996 | holiday_end_date = self._create_holiday_end_date(holiday_end) 997 | self.api.set_holiday(self.gw, holiday_end_date) 998 | self._set_holiday(holiday_end_date) 999 | 1000 | async def async_set_holiday(self, holiday_end: date): 1001 | """Async set holiday on device""" 1002 | holiday_end_date = self._create_holiday_end_date(holiday_end) 1003 | await self.api.async_set_holiday(self.gw, holiday_end_date) 1004 | self._set_holiday(holiday_end_date) 1005 | 1006 | def _calc_energy_account(self) -> dict[str, Any]: 1007 | """Calculate the energy account""" 1008 | calculated_heating_energy = 0 1009 | calculated_cooling_energy = 0 1010 | 1011 | for sequence in self.consumptions_sequences: 1012 | if sequence["p"] == ConsumptionTimeInterval.LAST_MONTH.value: 1013 | if sequence["k"] == ConsumptionType.CENTRAL_COOLING_TOTAL_ENERGY.value: 1014 | calculated_cooling_energy = sum(sequence["v"]) 1015 | 1016 | elif ( 1017 | sequence["k"] == ConsumptionType.CENTRAL_HEATING_TOTAL_ENERGY.value 1018 | ): 1019 | calculated_heating_energy = sum(sequence["v"]) 1020 | 1021 | return { 1022 | "LastMonth": [ 1023 | {"elect": calculated_heating_energy, "cool": calculated_cooling_energy} 1024 | ] 1025 | } 1026 | 1027 | def update_energy(self) -> None: 1028 | """Update the device energy settings from the cloud""" 1029 | super().update_energy() 1030 | 1031 | # These settings only for official clients 1032 | self.consumptions_settings = self.api.get_consumptions_settings(self.gw) 1033 | # Last month consumption in kwh 1034 | self.energy_account = self.api.get_energy_account(self.gw) 1035 | 1036 | if not self.energy_account.get("LastMonth"): 1037 | self.energy_account = self._calc_energy_account() 1038 | 1039 | async def async_update_energy(self) -> None: 1040 | """Async update the device energy settings from the cloud""" 1041 | (_, self.consumptions_settings, self.energy_account) = await asyncio.gather( 1042 | super().async_update_energy(), 1043 | # These settings only for official clients 1044 | self.api.async_get_consumptions_settings(self.gw), 1045 | # Last month consumption in kwh 1046 | self.api.async_get_energy_account(self.gw), 1047 | ) 1048 | 1049 | if not self.energy_account.get("LastMonth"): 1050 | self.energy_account = self._calc_energy_account() 1051 | -------------------------------------------------------------------------------- /ariston/lux2_device.py: -------------------------------------------------------------------------------- 1 | """Lux2 device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | 6 | from .const import EvoDeviceProperties 7 | from .evo_device import AristonEvoDevice 8 | 9 | _LOGGER = logging.getLogger(__name__) 10 | 11 | class AristonLux2Device(AristonEvoDevice): 12 | """Class representing a physical device, it's state and properties.""" 13 | 14 | def set_water_heater_power_option(self, power_option: bool): 15 | """Set water heater power option""" 16 | self.api.set_lux_power_option(self.gw, power_option) 17 | self.data[EvoDeviceProperties.PWR_OPT] = power_option 18 | 19 | async def async_set_water_heater_power_option(self, power_option: bool): 20 | """Async set water heater power option""" 21 | await self.api.async_set_lux_power_option(self.gw, power_option) 22 | self.data[EvoDeviceProperties.PWR_OPT] = power_option 23 | -------------------------------------------------------------------------------- /ariston/lux_device.py: -------------------------------------------------------------------------------- 1 | """Lux device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from typing import Optional 6 | 7 | from .const import ( 8 | LuxPlantMode, 9 | EvoDeviceProperties, 10 | EvoLydosDeviceProperties, 11 | WaterHeaterMode 12 | ) 13 | from .evo_device import AristonEvoDevice 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | 17 | 18 | class AristonLuxDevice(AristonEvoDevice): 19 | """Class representing a physical device, it's state and properties.""" 20 | 21 | @property 22 | def water_heater_mode(self) -> type[WaterHeaterMode]: 23 | """Return the water heater mode class""" 24 | return LuxPlantMode 25 | 26 | def set_water_heater_operation_mode(self, operation_mode: str): 27 | """Set water heater operation mode""" 28 | self.api.set_evo_mode(self.gw, LuxPlantMode[operation_mode]) 29 | self.data[EvoDeviceProperties.MODE] = LuxPlantMode[operation_mode].value 30 | 31 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 32 | """Async set water heater operation mode""" 33 | await self.api.async_set_evo_mode(self.gw, LuxPlantMode[operation_mode]) 34 | self.data[EvoDeviceProperties.MODE] = LuxPlantMode[operation_mode].value 35 | 36 | @property 37 | def water_heater_target_temperature(self) -> Optional[float]: 38 | """Get water heater target temperature""" 39 | if self.data.get(EvoDeviceProperties.MODE) == LuxPlantMode.BOOST: 40 | return self.water_heater_maximum_setpoint_temperature_maximum 41 | else: 42 | return self.data.get(EvoLydosDeviceProperties.REQ_TEMP, None) 43 | -------------------------------------------------------------------------------- /ariston/lydos_device.py: -------------------------------------------------------------------------------- 1 | """Evo device class for Ariston module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from .const import ( 8 | EvoDeviceProperties, 9 | LuxPlantMode, 10 | WaterHeaterMode, 11 | ) 12 | from .evo_device import AristonEvoDevice 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | class AristonLydosDevice(AristonEvoDevice): 18 | """Class representing a physical Lydos Wi-Fi device, it's state and properties.""" 19 | 20 | @property 21 | def water_heater_mode(self) -> type[WaterHeaterMode]: 22 | """Return the water heater mode class""" 23 | return LuxPlantMode 24 | 25 | def set_water_heater_operation_mode(self, operation_mode: str): 26 | """Set water heater operation mode""" 27 | self.api.set_evo_mode(self.gw, self.water_heater_mode[operation_mode]) 28 | self.data[EvoDeviceProperties.MODE] = self.water_heater_mode[ 29 | operation_mode 30 | ].value 31 | 32 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 33 | """Async set water heater operation mode""" 34 | await self.api.async_set_evo_mode( 35 | self.gw, self.water_heater_mode[operation_mode] 36 | ) 37 | self.data[EvoDeviceProperties.MODE] = self.water_heater_mode[ 38 | operation_mode 39 | ].value 40 | -------------------------------------------------------------------------------- /ariston/lydos_hybrid_device.py: -------------------------------------------------------------------------------- 1 | """Lydos hybrid device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from typing import Optional 6 | 7 | from .const import ( 8 | ConsumptionTimeInterval, 9 | ConsumptionType, 10 | LydosPlantMode, 11 | PlantData, 12 | SeDeviceSettings, 13 | LydosDeviceProperties, 14 | ) 15 | from .evo_lydos_device import AristonEvoLydosDevice 16 | 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | 20 | class AristonLydosHybridDevice(AristonEvoLydosDevice): 21 | """Class representing a physical device, it's state and properties.""" 22 | 23 | @property 24 | def plant_data(self) -> PlantData: 25 | """Final string to get plant data""" 26 | return PlantData.Se 27 | 28 | @property 29 | def anti_legionella_on_off(self) -> str: 30 | """Final string to get anti-legionella-on-off""" 31 | return SeDeviceSettings.SE_ANTILEGIONELLA_ON_OFF 32 | 33 | @property 34 | def consumption_type(self) -> str: 35 | """String to get consumption type""" 36 | return "DhwHeatingPumpElec%2CDhwResistorElec" 37 | 38 | @property 39 | def water_heater_mode(self) -> type[LydosPlantMode]: 40 | """Return the water heater mode class""" 41 | return LydosPlantMode 42 | 43 | @property 44 | def max_setpoint_temp(self) -> str: 45 | return SeDeviceSettings.SE_MAX_SETPOINT_TEMPERATURE 46 | 47 | @property 48 | def water_heater_maximum_setpoint_temperature_minimum(self) -> Optional[float]: 49 | """Get water heater maximum setpoint temperature minimum""" 50 | return self.plant_settings.get( 51 | SeDeviceSettings.SE_MAX_SETPOINT_TEMPERATURE_MIN, None 52 | ) 53 | 54 | @property 55 | def water_heater_maximum_setpoint_temperature_maximum(self) -> Optional[float]: 56 | """Get water heater maximum setpoint maximum temperature""" 57 | return self.plant_settings.get( 58 | SeDeviceSettings.SE_MAX_SETPOINT_TEMPERATURE_MAX, None 59 | ) 60 | 61 | @property 62 | def electric_consumption_for_water_last_two_hours(self) -> int: 63 | """Get electric consumption for water last value""" 64 | return self._get_consumption_sequence_last_value( 65 | ConsumptionType.DOMESTIC_HOT_WATER_HEATING_PUMP_ELECTRICITY, 66 | ConsumptionTimeInterval.LAST_DAY, 67 | ) 68 | 69 | @property 70 | def permanent_boost_value(self) -> int: 71 | """Get permanent boost value""" 72 | return self.plant_settings.get(SeDeviceSettings.SE_PERMANENT_BOOST_ON_OFF, 0) 73 | 74 | @property 75 | def anti_cooling_value(self) -> int: 76 | """Get anti cooling value""" 77 | return self.plant_settings.get(SeDeviceSettings.SE_ANTI_COOLING_ON_OFF, 0) 78 | 79 | @property 80 | def anti_cooling_temperature_value(self) -> int: 81 | """Get anti cooling temperature value""" 82 | return self.plant_settings.get(SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE, 0) 83 | 84 | @property 85 | def anti_cooling_temperature_maximum(self) -> int: 86 | """Get anti cooling temperature maximum""" 87 | return self.plant_settings.get(SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE_MAX, 0) 88 | 89 | @property 90 | def anti_cooling_temperature_minimum(self) -> int: 91 | """Get anti cooling temperature minimum""" 92 | return self.plant_settings.get(SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE_MIN, 0) 93 | 94 | @property 95 | def night_mode_value(self) -> int: 96 | """Get night mode value""" 97 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_MODE_ON_OFF, 0) 98 | 99 | @property 100 | def night_mode_begin_as_minutes_value(self) -> int: 101 | """Get night mode begin as minutes value""" 102 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_BEGIN_AS_MINUTES, 0) 103 | 104 | @property 105 | def night_mode_begin_max_as_minutes_value(self) -> int: 106 | """Get night mode begin max as minutes value""" 107 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_BEGIN_MAX_AS_MINUTES, 0) 108 | 109 | @property 110 | def night_mode_begin_min_as_minutes_value(self) -> int: 111 | """Get night mode begin min as minutes value""" 112 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_BEGIN_MIN_AS_MINUTES, 0) 113 | 114 | @property 115 | def night_mode_end_as_minutes_value(self) -> int: 116 | """Get night mode end as minutes value""" 117 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_END_AS_MINUTES, 0) 118 | 119 | @property 120 | def night_mode_end_max_as_minutes_value(self) -> int: 121 | """Get night mode end max as minutes value""" 122 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_END_MAX_AS_MINUTES, 0) 123 | 124 | @property 125 | def night_mode_end_min_as_minutes_value(self) -> int: 126 | """Get night mode end min as minutes value""" 127 | return self.plant_settings.get(SeDeviceSettings.SE_NIGHT_END_MIN_AS_MINUTES, 0) 128 | 129 | def set_water_heater_operation_mode(self, operation_mode: str): 130 | """Set water heater operation mode""" 131 | self.api.set_lydos_mode(self.gw, LydosPlantMode[operation_mode]) 132 | self.data[LydosDeviceProperties.MODE] = LydosPlantMode[operation_mode].value 133 | 134 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 135 | """Async set water heater operation mode""" 136 | await self.api.async_set_lydos_mode(self.gw, LydosPlantMode[operation_mode]) 137 | self.data[LydosDeviceProperties.MODE] = LydosPlantMode[operation_mode].value 138 | 139 | def set_water_heater_temperature(self, temperature: float): 140 | """Set water heater temperature""" 141 | self.api.set_lydos_temperature(self.gw, temperature) 142 | self.data[LydosDeviceProperties.REQ_TEMP] = temperature 143 | 144 | async def async_set_water_heater_temperature(self, temperature: float): 145 | """Async set water heater temperature""" 146 | await self.api.async_set_lydos_temperature(self.gw, temperature) 147 | self.data[LydosDeviceProperties.REQ_TEMP] = temperature 148 | 149 | def set_permanent_boost_value(self, boost: float) -> None: 150 | """Set permanent boost value""" 151 | self.api.set_velis_plant_setting( 152 | self.plant_data, 153 | self.gw, 154 | SeDeviceSettings.SE_PERMANENT_BOOST_ON_OFF, 155 | 1.0 if boost else 0.0, 156 | 1.0 if self.permanent_boost_value else 0.0, 157 | ) 158 | self.plant_settings[SeDeviceSettings.SE_PERMANENT_BOOST_ON_OFF] = boost 159 | 160 | async def async_set_permanent_boost_value(self, boost: float) -> None: 161 | """Async set permanent boost value""" 162 | await self.api.async_set_velis_plant_setting( 163 | self.plant_data, 164 | self.gw, 165 | SeDeviceSettings.SE_PERMANENT_BOOST_ON_OFF, 166 | 1.0 if boost else 0.0, 167 | 1.0 if self.permanent_boost_value else 0.0, 168 | ) 169 | self.plant_settings[SeDeviceSettings.SE_PERMANENT_BOOST_ON_OFF] = boost 170 | 171 | def set_anti_cooling_value(self, anti_cooling: float) -> None: 172 | """Set anti cooling value""" 173 | self.api.set_velis_plant_setting( 174 | self.plant_data, 175 | self.gw, 176 | SeDeviceSettings.SE_ANTI_COOLING_ON_OFF, 177 | 1.0 if anti_cooling else 0.0, 178 | 1.0 if self.anti_cooling_value else 0.0, 179 | ) 180 | self.plant_settings[SeDeviceSettings.SE_ANTI_COOLING_ON_OFF] = anti_cooling 181 | 182 | async def async_set_anti_cooling_value(self, anti_cooling: float) -> None: 183 | """Async set anti cooling value""" 184 | await self.api.async_set_velis_plant_setting( 185 | self.plant_data, 186 | self.gw, 187 | SeDeviceSettings.SE_ANTI_COOLING_ON_OFF, 188 | 1.0 if anti_cooling else 0.0, 189 | 1.0 if self.anti_cooling_value else 0.0, 190 | ) 191 | self.plant_settings[SeDeviceSettings.SE_ANTI_COOLING_ON_OFF] = anti_cooling 192 | 193 | def set_cooling_temperature_value(self, temperature: float) -> None: 194 | """Set cooling temperature value""" 195 | self.api.set_velis_plant_setting( 196 | self.plant_data, 197 | self.gw, 198 | SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE, 199 | temperature, 200 | self.anti_cooling_temperature_value, 201 | ) 202 | self.plant_settings[SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE] = temperature 203 | 204 | async def async_set_cooling_temperature_value(self, temperature: float) -> None: 205 | """Async set cooling temperature value""" 206 | await self.api.async_set_velis_plant_setting( 207 | self.plant_data, 208 | self.gw, 209 | SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE, 210 | temperature, 211 | self.anti_cooling_temperature_value, 212 | ) 213 | self.plant_settings[SeDeviceSettings.SE_ANTI_COOLING_TEMPERATURE] = temperature 214 | 215 | def set_night_mode_value(self, night_mode: float) -> None: 216 | """Set night mode value""" 217 | self.api.set_velis_plant_setting( 218 | self.plant_data, 219 | self.gw, 220 | SeDeviceSettings.SE_NIGHT_MODE_ON_OFF, 221 | 1.0 if night_mode else 0.0, 222 | 1.0 if self.night_mode_value else 0.0, 223 | ) 224 | self.plant_settings[SeDeviceSettings.SE_NIGHT_MODE_ON_OFF] = night_mode 225 | 226 | async def async_set_night_mode_value(self, night_mode: float) -> None: 227 | """Async set night mode value""" 228 | await self.api.async_set_velis_plant_setting( 229 | self.plant_data, 230 | self.gw, 231 | SeDeviceSettings.SE_NIGHT_MODE_ON_OFF, 232 | 1.0 if night_mode else 0.0, 233 | 1.0 if self.night_mode_value else 0.0, 234 | ) 235 | self.plant_settings[SeDeviceSettings.SE_NIGHT_MODE_ON_OFF] = night_mode 236 | 237 | def set_night_mode_begin_as_minutes_value(self, night_mode_begin_as_minutes: int) -> None: 238 | """Set night mode begin as minutes value""" 239 | self.api.set_velis_plant_setting( 240 | self.plant_data, 241 | self.gw, 242 | SeDeviceSettings.SE_NIGHT_BEGIN_AS_MINUTES, 243 | night_mode_begin_as_minutes, 244 | self.night_mode_begin_as_minutes_value, 245 | ) 246 | self.plant_settings[SeDeviceSettings.SE_NIGHT_BEGIN_AS_MINUTES] = night_mode_begin_as_minutes 247 | 248 | async def async_set_night_mode_begin_as_minutes_value(self, night_mode_begin_as_minutes: int) -> None: 249 | """Async set night mode begin as minutes value""" 250 | await self.api.async_set_velis_plant_setting( 251 | self.plant_data, 252 | self.gw, 253 | SeDeviceSettings.SE_NIGHT_BEGIN_AS_MINUTES, 254 | night_mode_begin_as_minutes, 255 | self.night_mode_begin_as_minutes_value, 256 | ) 257 | self.plant_settings[SeDeviceSettings.SE_NIGHT_BEGIN_AS_MINUTES] = night_mode_begin_as_minutes 258 | 259 | def set_night_mode_end_as_minutes_value(self, night_mode_end_as_minutes: int) -> None: 260 | """Set night mode end as minutes value""" 261 | self.api.set_velis_plant_setting( 262 | self.plant_data, 263 | self.gw, 264 | SeDeviceSettings.SE_NIGHT_END_AS_MINUTES, 265 | night_mode_end_as_minutes, 266 | self.night_mode_end_as_minutes_value, 267 | ) 268 | self.plant_settings[SeDeviceSettings.SE_NIGHT_END_AS_MINUTES] = night_mode_end_as_minutes 269 | 270 | async def async_set_night_mode_end_as_minutes_value(self, night_mode_end_as_minutes: int) -> None: 271 | """Async set night mode end as minutes value""" 272 | await self.api.async_set_velis_plant_setting( 273 | self.plant_data, 274 | self.gw, 275 | SeDeviceSettings.SE_NIGHT_END_AS_MINUTES, 276 | night_mode_end_as_minutes, 277 | self.night_mode_end_as_minutes_value, 278 | ) 279 | self.plant_settings[SeDeviceSettings.SE_NIGHT_END_AS_MINUTES] = night_mode_end_as_minutes 280 | -------------------------------------------------------------------------------- /ariston/nuos_split_device.py: -------------------------------------------------------------------------------- 1 | """Nuos split device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from typing import Optional 6 | 7 | from .velis_device import AristonVelisDevice 8 | from .const import ( 9 | NuosSplitOperativeMode, 10 | NuosSplitProperties, 11 | PlantData, 12 | SlpDeviceSettings, 13 | ) 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | 17 | 18 | class AristonNuosSplitDevice(AristonVelisDevice): 19 | """Class representing a physical device, it's state and properties.""" 20 | 21 | @property 22 | def plant_data(self) -> PlantData: 23 | """Final string to get plant data""" 24 | return PlantData.Slp 25 | 26 | @property 27 | def anti_legionella_on_off(self) -> str: 28 | """Final string to get anti-legionella-on-off""" 29 | return SlpDeviceSettings.SLP_ANTILEGIONELLA_ON_OFF 30 | 31 | @property 32 | def consumption_type(self) -> str: 33 | """String to get consumption type""" 34 | return "DhwHeatingPumpElec%2CDhwResistorElec" 35 | 36 | @property 37 | def water_heater_mode(self) -> type[NuosSplitOperativeMode]: 38 | """Return the water heater mode class""" 39 | return NuosSplitOperativeMode 40 | 41 | @property 42 | def max_setpoint_temp(self) -> str: 43 | return SlpDeviceSettings.SLP_MAX_SETPOINT_TEMPERATURE 44 | 45 | @property 46 | def water_heater_current_temperature(self) -> Optional[float]: 47 | """Get water heater current temperature""" 48 | return self.data.get(NuosSplitProperties.WATER_TEMP, None) 49 | 50 | @property 51 | def water_heater_target_temperature(self) -> Optional[float]: 52 | """Get water heater target temperature""" 53 | return self.data.get(NuosSplitProperties.COMFORT_TEMP, None) 54 | 55 | @property 56 | def water_heater_reduced_temperature(self) -> Optional[float]: 57 | """Get water heater reduced temperature""" 58 | return self.data.get(NuosSplitProperties.REDUCED_TEMP, None) 59 | 60 | @property 61 | def water_heater_maximum_setpoint_temperature_minimum(self) -> Optional[float]: 62 | """Get water heater maximum setpoint temperature minimum""" 63 | return self.plant_settings.get( 64 | SlpDeviceSettings.SLP_MAX_SETPOINT_TEMPERATURE_MIN, None 65 | ) 66 | 67 | @property 68 | def water_heater_maximum_setpoint_temperature_maximum(self) -> Optional[float]: 69 | """Get water heater maximum setpoint maximum temperature""" 70 | return self.plant_settings.get( 71 | SlpDeviceSettings.SLP_MAX_SETPOINT_TEMPERATURE_MAX, None 72 | ) 73 | 74 | @property 75 | def water_heater_minimum_setpoint_temperature(self) -> Optional[float]: 76 | """Get water heater minimum setpoint temperature value""" 77 | return self.plant_settings.get( 78 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE, None 79 | ) 80 | 81 | @property 82 | def water_heater_minimum_setpoint_temperature_minimum(self) -> Optional[float]: 83 | """Get water heater minimum setpoint temperature minimum""" 84 | return self.plant_settings.get( 85 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE_MIN, None 86 | ) 87 | 88 | @property 89 | def water_heater_minimum_setpoint_temperature_maximum(self) -> Optional[float]: 90 | """Get water heater minimum setpoint maximum temperature""" 91 | return self.plant_settings.get( 92 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE_MAX, None 93 | ) 94 | 95 | @property 96 | def water_heater_preheating_on_off(self) -> Optional[bool]: 97 | """Get water heater preheating on off""" 98 | return self.plant_settings.get(SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF, None) 99 | 100 | @property 101 | def water_heater_heating_rate(self) -> Optional[float]: 102 | """Get water heater heating rate""" 103 | return self.plant_settings.get(SlpDeviceSettings.SLP_HEATING_RATE, None) 104 | 105 | @property 106 | def water_heater_boost(self) -> Optional[bool]: 107 | """Get water heater boost""" 108 | return self.data.get(NuosSplitProperties.BOOST_ON, None) 109 | 110 | @property 111 | def water_heater_mode_value(self) -> Optional[int]: 112 | """Get water heater mode value""" 113 | return self.data.get(NuosSplitProperties.OP_MODE, None) 114 | 115 | def set_water_heater_boost(self, boost: bool): 116 | """Set water heater boost""" 117 | self.api.set_nous_boost(self.gw, boost) 118 | self.data[NuosSplitProperties.BOOST_ON] = boost 119 | 120 | async def async_set_water_heater_boost(self, boost: bool): 121 | """Set water heater boost""" 122 | await self.api.async_set_nous_boost(self.gw, boost) 123 | self.data[NuosSplitProperties.BOOST_ON] = boost 124 | 125 | def _set_water_heater_temperature(self, temperature: float, reduced: float): 126 | """Set water heater temperature""" 127 | self.api.set_nuos_temperature(self.gw, temperature, reduced, self.water_heater_target_temperature, self.water_heater_reduced_temperature) 128 | self.data[NuosSplitProperties.PROC_REQ_TEMP] = temperature 129 | self.data[NuosSplitProperties.REDUCED_TEMP] = reduced 130 | 131 | def set_water_heater_temperature(self, temperature: float): 132 | """Set water heater temperature""" 133 | if len(self.data) == 0: 134 | self.update_state() 135 | reduced = self.water_heater_reduced_temperature 136 | if reduced is None: 137 | reduced = 0 138 | self._set_water_heater_temperature(temperature, reduced) 139 | 140 | async def _async_set_water_heater_temperature( 141 | self, temperature: float, reduced: float 142 | ): 143 | """Async set water heater temperature""" 144 | await self.api.async_set_nuos_temperature(self.gw, temperature, reduced, self.water_heater_target_temperature, self.water_heater_reduced_temperature) 145 | self.data[NuosSplitProperties.PROC_REQ_TEMP] = temperature 146 | self.data[NuosSplitProperties.REDUCED_TEMP] = reduced 147 | 148 | async def async_set_water_heater_temperature(self, temperature: float): 149 | """Async set water heater temperature""" 150 | if len(self.data) == 0: 151 | await self.async_update_state() 152 | reduced = self.water_heater_reduced_temperature 153 | if reduced is None: 154 | reduced = 0 155 | await self._async_set_water_heater_temperature(temperature, reduced) 156 | 157 | def set_water_heater_reduced_temperature(self, temperature: float): 158 | """Set water heater reduced temperature""" 159 | if len(self.data) == 0: 160 | self.update_state() 161 | current = self.water_heater_current_temperature 162 | if current is None: 163 | current = 0 164 | self._set_water_heater_temperature(current, temperature) 165 | 166 | async def async_set_water_heater_reduced_temperature(self, temperature: float): 167 | """Set water heater reduced temperature""" 168 | if len(self.data) == 0: 169 | await self.async_update_state() 170 | current = self.water_heater_current_temperature 171 | if current is None: 172 | current = self.water_heater_minimum_temperature 173 | await self._async_set_water_heater_temperature(current, temperature) 174 | 175 | def set_water_heater_operation_mode(self, operation_mode: str): 176 | """Set water heater operation mode""" 177 | self.api.set_nuos_mode(self.gw, NuosSplitOperativeMode[operation_mode]) 178 | self.data[NuosSplitProperties.MODE] = NuosSplitOperativeMode[ 179 | operation_mode 180 | ].value 181 | 182 | async def async_set_water_heater_operation_mode(self, operation_mode: str): 183 | """Async set water heater operation mode""" 184 | await self.api.async_set_nuos_mode( 185 | self.gw, NuosSplitOperativeMode[operation_mode] 186 | ) 187 | self.data[NuosSplitProperties.MODE] = NuosSplitOperativeMode[ 188 | operation_mode 189 | ].value 190 | 191 | def set_min_setpoint_temp(self, min_setpoint_temp: float): 192 | """Set water heater minimum setpoint temperature""" 193 | self.api.set_velis_plant_setting( 194 | self.plant_data, 195 | self.gw, 196 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE, 197 | min_setpoint_temp, 198 | self.plant_settings[SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE], 199 | ) 200 | self.plant_settings[ 201 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE 202 | ] = min_setpoint_temp 203 | 204 | async def async_set_min_setpoint_temp(self, min_setpoint_temp: float): 205 | """Async set water heater minimum setpoint temperature""" 206 | await self.api.async_set_velis_plant_setting( 207 | self.plant_data, 208 | self.gw, 209 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE, 210 | min_setpoint_temp, 211 | self.plant_settings[SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE], 212 | ) 213 | self.plant_settings[ 214 | SlpDeviceSettings.SLP_MIN_SETPOINT_TEMPERATURE 215 | ] = min_setpoint_temp 216 | 217 | def set_preheating(self, preheating: bool): 218 | """Set water heater preheating""" 219 | self.api.set_velis_plant_setting( 220 | self.plant_data, 221 | self.gw, 222 | SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF, 223 | preheating, 224 | self.plant_settings[SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF], 225 | ) 226 | self.plant_settings[SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF] = preheating 227 | 228 | async def async_set_preheating(self, preheating: bool): 229 | """Async set water heater preheating""" 230 | await self.api.async_set_velis_plant_setting( 231 | self.plant_data, 232 | self.gw, 233 | SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF, 234 | preheating, 235 | self.plant_settings[SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF], 236 | ) 237 | self.plant_settings[SlpDeviceSettings.SLP_PRE_HEATING_ON_OFF] = preheating 238 | 239 | def set_heating_rate(self, heating_rate: float): 240 | """Set water heater heating rate""" 241 | self.api.set_velis_plant_setting( 242 | self.plant_data, 243 | self.gw, 244 | SlpDeviceSettings.SLP_HEATING_RATE, 245 | heating_rate, 246 | self.plant_settings[SlpDeviceSettings.SLP_HEATING_RATE], 247 | ) 248 | self.plant_settings[SlpDeviceSettings.SLP_HEATING_RATE] = heating_rate 249 | 250 | async def async_set_heating_rate(self, heating_rate: float): 251 | """Async set water heater heating rate""" 252 | await self.api.async_set_velis_plant_setting( 253 | self.plant_data, 254 | self.gw, 255 | SlpDeviceSettings.SLP_HEATING_RATE, 256 | heating_rate, 257 | self.plant_settings[SlpDeviceSettings.SLP_HEATING_RATE], 258 | ) 259 | self.plant_settings[SlpDeviceSettings.SLP_HEATING_RATE] = heating_rate 260 | -------------------------------------------------------------------------------- /ariston/velis_base_device.py: -------------------------------------------------------------------------------- 1 | """Velis device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from abc import ABC, abstractmethod 6 | from typing import Any, Optional 7 | 8 | from .ariston_api import AristonAPI 9 | from .const import ( 10 | CustomDeviceFeatures, 11 | DeviceFeatures, 12 | PlantData, 13 | VelisBaseDeviceProperties, 14 | WaterHeaterMode, 15 | ) 16 | from .base_device import AristonBaseDevice 17 | 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | 21 | class AristonVelisBaseDevice(AristonBaseDevice, ABC): 22 | """Class representing a physical device, it's state and properties.""" 23 | 24 | def __init__( 25 | self, 26 | api: AristonAPI, 27 | attributes: dict[str, Any], 28 | ) -> None: 29 | super().__init__(api, attributes) 30 | self.plant_settings: dict[str, Any] = dict() 31 | 32 | @property 33 | @abstractmethod 34 | def plant_data(self) -> PlantData: 35 | """Final string to get plant data""" 36 | 37 | @property 38 | @abstractmethod 39 | def water_heater_mode(self) -> type[WaterHeaterMode]: 40 | """Return the water heater mode class""" 41 | 42 | def update_state(self) -> None: 43 | """Update the device states from the cloud""" 44 | self.data = self.api.get_velis_plant_data(self.plant_data, self.gw) 45 | 46 | async def async_update_state(self) -> None: 47 | """Async update the device states from the cloud""" 48 | self.data = await self.api.async_get_velis_plant_data(self.plant_data, self.gw) 49 | 50 | def update_settings(self) -> None: 51 | """Get device settings wrapper""" 52 | self.plant_settings = self.api.get_velis_plant_settings( 53 | self.plant_data, self.gw 54 | ) 55 | 56 | async def async_update_settings(self) -> None: 57 | """Get device settings wrapper""" 58 | self.plant_settings = await self.api.async_get_velis_plant_settings( 59 | self.plant_data, self.gw 60 | ) 61 | 62 | def set_power(self, power: bool): 63 | """Set water heater power""" 64 | self.api.set_velis_power(self.plant_data, self.gw, power) 65 | self.data[VelisBaseDeviceProperties.ON] = power 66 | 67 | async def async_set_power(self, power: bool) -> None: 68 | """Async set water heater power""" 69 | await self.api.async_set_velis_power(self.plant_data, self.gw, power) 70 | self.data[VelisBaseDeviceProperties.ON] = power 71 | 72 | @property 73 | def water_heater_mode_operation_texts(self) -> list[str]: 74 | """Get water heater operation mode texts""" 75 | return [flag.name for flag in self.water_heater_mode] 76 | 77 | @property 78 | def water_heater_mode_options(self) -> list[int]: 79 | """Get water heater operation options""" 80 | return [flag.value for flag in self.water_heater_mode] 81 | 82 | def get_features(self) -> None: 83 | """Get device features wrapper""" 84 | super().get_features() 85 | self.custom_features[CustomDeviceFeatures.HAS_DHW] = True 86 | self.features[DeviceFeatures.DHW_MODE_CHANGEABLE] = True 87 | self.update_settings() 88 | 89 | async def async_get_features(self) -> None: 90 | """Async get device features wrapper""" 91 | await super().async_get_features() 92 | self.custom_features[CustomDeviceFeatures.HAS_DHW] = True 93 | self.features[DeviceFeatures.DHW_MODE_CHANGEABLE] = True 94 | await self.async_update_settings() 95 | 96 | @property 97 | def water_heater_mode_value(self) -> Optional[int]: 98 | """Get water heater mode value""" 99 | return self.data.get(VelisBaseDeviceProperties.MODE, None) 100 | 101 | @property 102 | def water_heater_power_value(self) -> Optional[bool]: 103 | """Get water heater power value""" 104 | return self.data.get(VelisBaseDeviceProperties.ON, None) 105 | -------------------------------------------------------------------------------- /ariston/velis_device.py: -------------------------------------------------------------------------------- 1 | """Velis device class for Ariston module.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | from abc import ABC, abstractmethod 6 | from typing import Any, Optional 7 | 8 | from .ariston_api import AristonAPI 9 | from .const import VelisDeviceProperties 10 | from .velis_base_device import AristonVelisBaseDevice 11 | from .device import AristonDevice 12 | 13 | _LOGGER = logging.getLogger(__name__) 14 | 15 | 16 | class AristonVelisDevice(AristonDevice, AristonVelisBaseDevice, ABC): 17 | """Class representing a physical device, it's state and properties.""" 18 | 19 | def __init__( 20 | self, 21 | api: AristonAPI, 22 | attributes: dict[str, Any], 23 | ) -> None: 24 | super().__init__(api, attributes) 25 | self.plant_settings: dict[str, Any] = dict() 26 | 27 | @property 28 | @abstractmethod 29 | def anti_legionella_on_off(self) -> str: 30 | """Final string to get anti-legionella-on-off""" 31 | 32 | @property 33 | @abstractmethod 34 | def max_setpoint_temp(self) -> str: 35 | """Final string to get max setpoint temperature""" 36 | 37 | @property 38 | def water_anti_leg_value(self) -> Optional[bool]: 39 | """Get water heater anti-legionella value""" 40 | return self.plant_settings.get(self.anti_legionella_on_off, None) 41 | 42 | @property 43 | def proc_req_temp_value(self) -> Optional[float]: 44 | """Get process requested tempereature value""" 45 | return self.data.get(VelisDeviceProperties.PROC_REQ_TEMP, None) 46 | 47 | def set_antilegionella(self, anti_leg: bool): 48 | """Set water heater anti-legionella""" 49 | self.api.set_velis_plant_setting( 50 | self.plant_data, 51 | self.gw, 52 | self.anti_legionella_on_off, 53 | 1.0 if anti_leg else 0.0, 54 | 1.0 if self.plant_settings[self.anti_legionella_on_off] else 0.0, 55 | ) 56 | self.plant_settings[self.anti_legionella_on_off] = anti_leg 57 | 58 | async def async_set_antilegionella(self, anti_leg: bool): 59 | """Async set water heater anti-legionella""" 60 | await self.api.async_set_velis_plant_setting( 61 | self.plant_data, 62 | self.gw, 63 | self.anti_legionella_on_off, 64 | 1.0 if anti_leg else 0.0, 65 | 1.0 if self.plant_settings[self.anti_legionella_on_off] else 0.0, 66 | ) 67 | self.plant_settings[self.anti_legionella_on_off] = anti_leg 68 | 69 | def set_max_setpoint_temp(self, max_setpoint_temp: float): 70 | """Set water heater maximum setpoint temperature""" 71 | self.api.set_velis_plant_setting( 72 | self.plant_data, 73 | self.gw, 74 | self.max_setpoint_temp, 75 | max_setpoint_temp, 76 | self.plant_settings[self.max_setpoint_temp], 77 | ) 78 | self.plant_settings[self.max_setpoint_temp] = max_setpoint_temp 79 | 80 | async def async_set_max_setpoint_temp(self, max_setpoint_temp: float): 81 | """Async set water heater maximum setpoint temperature""" 82 | await self.api.async_set_velis_plant_setting( 83 | self.plant_data, 84 | self.gw, 85 | self.max_setpoint_temp, 86 | max_setpoint_temp, 87 | self.plant_settings[self.max_setpoint_temp], 88 | ) 89 | self.plant_settings[self.max_setpoint_temp] = max_setpoint_temp 90 | 91 | @property 92 | @abstractmethod 93 | def water_heater_maximum_setpoint_temperature_minimum(self) -> Optional[float]: 94 | """Get water heater maximum setpoint temperature minimum""" 95 | raise NotImplementedError 96 | 97 | @property 98 | @abstractmethod 99 | def water_heater_maximum_setpoint_temperature_maximum(self) -> Optional[float]: 100 | """Get water heater maximum setpoint maximum temperature""" 101 | raise NotImplementedError 102 | 103 | @property 104 | def water_heater_maximum_setpoint_temperature(self) -> Optional[float]: 105 | """Get water heater maximum setpoint temperature value""" 106 | return self.plant_settings.get(self.max_setpoint_temp, None) 107 | 108 | @property 109 | def water_heater_minimum_temperature(self) -> float: 110 | """Get water heater minimum temperature""" 111 | return 40.0 112 | 113 | @property 114 | def water_heater_maximum_temperature(self) -> Optional[float]: 115 | """Get water heater maximum temperature""" 116 | return self.water_heater_maximum_setpoint_temperature 117 | 118 | @property 119 | def water_heater_temperature_step(self) -> int: 120 | """Get water heater temperature step""" 121 | return 1 122 | 123 | @property 124 | def water_heater_temperature_decimals(self) -> int: 125 | """Get water heater temperature decimals""" 126 | return 0 127 | 128 | @property 129 | def water_heater_temperature_unit(self) -> str: 130 | """Get water heater temperature unit""" 131 | return "°C" 132 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "flit_core.buildapi" 3 | requires = ["flit_core >=3.2,<4"] 4 | 5 | [project] 6 | name = "ariston" 7 | readme = "README.md" 8 | authors = [ 9 | { name = "Tamás Füstös", email = "fustom@gmail.com" } 10 | ] 11 | dependencies = [ 12 | "aiohttp", 13 | "requests" 14 | ] 15 | requires-python = ">=3.9" 16 | license = { file = "LICENSE" } 17 | version = "0.19.9" 18 | dynamic = ['description'] 19 | 20 | [project.urls] 21 | Repository = "https://github.com/fustom/python-ariston-api.git" 22 | Issues = "https://github.com/fustom/python-ariston-api/issues" 23 | --------------------------------------------------------------------------------