├── .gitattributes ├── requirements_dev.txt ├── custom_components ├── __init__.py └── waterkotte_heatpump │ ├── manifest.json │ ├── translations │ ├── nb.json │ ├── fr.json │ ├── en.json │ ├── heatpump.de.json │ └── heatpump.en.json │ ├── entity.py │ ├── const.py │ ├── services.yaml │ ├── api.py │ ├── switch.py │ ├── binary_sensor.py │ ├── config_flow.py │ ├── select.py │ ├── __init__.py │ ├── number.py │ ├── sensor.py │ └── service.py ├── tests ├── __init__.py ├── const.py ├── conftest.py ├── test_switch.py ├── test_init.py ├── test_api.py └── test_config_flow.py ├── pywaterkotte_dev.sh ├── logo.png ├── requirements_test.txt ├── .gitea ├── workflows │ ├── constraints.txt │ ├── labeler.yml │ └── release-drafter.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── issue.md ├── release-drafter.yml └── labels.yml ├── .github ├── workflows │ ├── constraints.txt │ ├── labeler.yml │ └── release-drafter.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── issue.md ├── release-drafter.yml └── labels.yml ├── .gitignore ├── hacs.json ├── .devcontainer ├── configuration.yaml └── devcontainer.json ├── .cookiecutter.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .pre-commit-config.yaml ├── LICENSE ├── setup.cfg ├── info.md ├── CONTRIBUTING.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | homeassistant 2 | -------------------------------------------------------------------------------- /custom_components/__init__.py: -------------------------------------------------------------------------------- 1 | """Dummy init so that pytest works.""" 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for Waterkotte Heatpump integration.""" 2 | -------------------------------------------------------------------------------- /pywaterkotte_dev.sh: -------------------------------------------------------------------------------- 1 | python3 -m pip install -e custom_components/pywaterkotte/ 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattisonmichael/waterkotte-integration/HEAD/logo.png -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | -r requirements_dev.txt 2 | pytest-homeassistant-custom-component==0.1.0 3 | -------------------------------------------------------------------------------- /.gitea/workflows/constraints.txt: -------------------------------------------------------------------------------- 1 | pip==21.0 2 | pre-commit==2.9.3 3 | black==20.8b1 4 | flake8==3.8.4 5 | reorder-python-imports==2.3.6 6 | -------------------------------------------------------------------------------- /.github/workflows/constraints.txt: -------------------------------------------------------------------------------- 1 | pip==21.0 2 | pre-commit==2.9.3 3 | black==20.8b1 4 | flake8==3.8.4 5 | reorder-python-imports==2.3.6 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | pythonenv* 3 | .python-version 4 | .coverage 5 | venv 6 | .venv 7 | custom_components/pywaterkotte/* 8 | custom_components/pywaterkott* 9 | core.* 10 | custom_components/waterkotte_heatpump/pywaterkotte -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Waterkotte Heatpump", 3 | "domains": ["binary_sensor", "sensor", "switch", "select", "number", "service"], 4 | "iot_class": "local_polling", 5 | "render_readme": true, 6 | "homeassistant": "2023.7.0", 7 | "hacs": "1.28.4" 8 | } 9 | -------------------------------------------------------------------------------- /.devcontainer/configuration.yaml: -------------------------------------------------------------------------------- 1 | default_config: 2 | 3 | logger: 4 | default: info 5 | logs: 6 | custom_components.waterkotte_heatpump: debug 7 | # If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/) 8 | debugpy: 9 | -------------------------------------------------------------------------------- /tests/const.py: -------------------------------------------------------------------------------- 1 | """Constants for Waterkotte Heatpump tests.""" 2 | from custom_components.waterkotte_heatpump.const import ( 3 | CONF_PASSWORD, 4 | ) 5 | from custom_components.waterkotte_heatpump.const import ( 6 | CONF_USERNAME, 7 | ) 8 | 9 | MOCK_CONFIG = {CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password"} 10 | -------------------------------------------------------------------------------- /.gitea/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: pip 8 | directory: "/.github/workflows" 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: pip 12 | directory: "/" 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: pip 8 | directory: "/.github/workflows" 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: pip 12 | directory: "/" 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "_template": "gh:oncleben31/cookiecutter-homeassistant-custom-component", 3 | "class_name_prefix": "WaterkotteHeatpump", 4 | "domain_name": "waterkotte_heatpump", 5 | "friendly_name": "Waterkotte Heatpump", 6 | "github_user": "pattisonmichael", 7 | "project_name": "waterkotte-heatpump", 8 | "test_suite": "yes", 9 | "version": "0.0.0" 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": true, 3 | "python.linting.enabled": true, 4 | "python.pythonPath": "venv/bin/python", 5 | "files.associations": { 6 | "*.yaml": "home-assistant" 7 | }, 8 | "python.analysis.extraPaths": [ 9 | "./custom_components/pywaterkotte/pywaterkotte" 10 | ], 11 | "python.linting.prospectorEnabled": false, 12 | "python.linting.flake8Enabled": false, 13 | "git.repositoryScanMaxDepth": -1 14 | } -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "waterkotte_heatpump", 3 | "name": "Waterkotte Heatpump", 4 | "documentation": "https://github.com/pattisonmichael/waterkotte-heatpump", 5 | "issue_tracker": "https://github.com/pattisonmichael/waterkotte-heatpump/issues", 6 | "dependencies": [], 7 | "config_flow": true, 8 | "codeowners": [ 9 | "@pattisonmichael" 10 | ], 11 | "requirements": ["pywaterkotte2==0.0.14"], 12 | "version": "0.1.16", 13 | "iot_class": "local_polling" 14 | } -------------------------------------------------------------------------------- /.gitea/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | **Describe the solution you'd like** 10 | A clear and concise description of what you want to happen. 11 | 12 | **Describe alternatives you've considered** 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | **Describe the solution you'd like** 10 | A clear and concise description of what you want to happen. 11 | 12 | **Describe alternatives you've considered** 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.gitea/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Manage labels 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths: 8 | - ".github/labels.yml" 9 | - ".github/workflows/labels.yml" 10 | 11 | jobs: 12 | labeler: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Run Labeler 18 | if: success() 19 | uses: crazy-max/ghaction-github-labeler@v4 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | yaml-file: .github/labels.yml 23 | skip-delete: false 24 | dry-run: false 25 | exclude: | 26 | help* 27 | *issue 28 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Manage labels 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths: 8 | - ".github/labels.yml" 9 | - ".github/workflows/labels.yml" 10 | 11 | jobs: 12 | labeler: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Run Labeler 18 | if: success() 19 | uses: crazy-max/ghaction-github-labeler@v4 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | yaml-file: .github/labels.yml 23 | skip-delete: false 24 | dry-run: false 25 | exclude: | 26 | help* 27 | *issue 28 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run Home Assistant on port 9123", 6 | "type": "shell", 7 | "command": "container start", 8 | "problemMatcher": [] 9 | }, 10 | { 11 | "label": "Run Home Assistant configuration against /config", 12 | "type": "shell", 13 | "command": "container check", 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Upgrade Home Assistant to latest dev", 18 | "type": "shell", 19 | "command": "container install", 20 | "problemMatcher": [] 21 | }, 22 | { 23 | "label": "Install a specific version of Home Assistant", 24 | "type": "shell", 25 | "command": "container set-version", 26 | "problemMatcher": [] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.gitea/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: ":boom: Breaking Changes" 3 | label: "breaking" 4 | - title: ":rocket: Features" 5 | label: "enhancement" 6 | - title: ":fire: Removals and Deprecations" 7 | label: "removal" 8 | - title: ":beetle: Fixes" 9 | label: "bug" 10 | - title: ":racehorse: Performance" 11 | label: "performance" 12 | - title: ":rotating_light: Testing" 13 | label: "testing" 14 | - title: ":construction_worker: Continuous Integration" 15 | label: "ci" 16 | - title: ":books: Documentation" 17 | label: "documentation" 18 | - title: ":hammer: Refactoring" 19 | label: "refactoring" 20 | - title: ":lipstick: Style" 21 | label: "style" 22 | - title: ":package: Dependencies" 23 | labels: 24 | - "dependencies" 25 | - "build" 26 | template: | 27 | ## Changes 28 | 29 | $CHANGES 30 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: ":boom: Breaking Changes" 3 | label: "breaking" 4 | - title: ":rocket: Features" 5 | label: "enhancement" 6 | - title: ":fire: Removals and Deprecations" 7 | label: "removal" 8 | - title: ":beetle: Fixes" 9 | label: "bug" 10 | - title: ":racehorse: Performance" 11 | label: "performance" 12 | - title: ":rotating_light: Testing" 13 | label: "testing" 14 | - title: ":construction_worker: Continuous Integration" 15 | label: "ci" 16 | - title: ":books: Documentation" 17 | label: "documentation" 18 | - title: ":hammer: Refactoring" 19 | label: "refactoring" 20 | - title: ":lipstick: Style" 21 | label: "style" 22 | - title: ":package: Dependencies" 23 | labels: 24 | - "dependencies" 25 | - "build" 26 | template: | 27 | ## Changes 28 | 29 | $CHANGES 30 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/translations/nb.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "title": "Waterkotte Heatpump", 6 | "description": "Hvis du trenger hjep til konfigurasjon ta en titt her: https://github.com/pattisonmichael/waterkotte-heatpump", 7 | "data": { 8 | "username": "Brukernavn", 9 | "password": "Passord" 10 | } 11 | } 12 | }, 13 | "error": { 14 | "auth": "Brukernavn/Passord er feil." 15 | }, 16 | "abort": { 17 | "single_instance_allowed": "Denne integrasjonen kan kun konfigureres en gang." 18 | } 19 | }, 20 | "options": { 21 | "step": { 22 | "user": { 23 | "data": { 24 | "binary_sensor": "Binær sensor aktivert", 25 | "sensor": "Sensor aktivert", 26 | "switch": "Bryter aktivert" 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "title": "Waterkotte Heatpump", 6 | "description": "Si vous avez besoin d'aide pour la configuration, regardez ici: https://github.com/pattisonmichael/waterkotte-heatpump", 7 | "data": { 8 | "username": "Identifiant", 9 | "password": "Mot de Passe" 10 | } 11 | } 12 | }, 13 | "error": { 14 | "auth": "Identifiant ou mot de passe erroné." 15 | }, 16 | "abort": { 17 | "single_instance_allowed": "Une seule instance est autorisée." 18 | } 19 | }, 20 | "options": { 21 | "step": { 22 | "user": { 23 | "data": { 24 | "binary_sensor": "Capteur binaire activé", 25 | "sensor": "Capteur activé", 26 | "switch": "Interrupteur activé" 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | // Example of attaching to local debug server 7 | "name": "Python: Attach Local", 8 | "type": "python", 9 | "request": "attach", 10 | "port": 5678, 11 | "host": "localhost", 12 | "pathMappings": [ 13 | { 14 | "localRoot": "${workspaceFolder}", 15 | "remoteRoot": "." 16 | } 17 | ] 18 | }, 19 | { 20 | // Example of attaching to my production server 21 | "name": "Python: Attach Remote", 22 | "type": "python", 23 | "request": "attach", 24 | "port": 5678, 25 | "host": "homeassistant.local", 26 | "pathMappings": [ 27 | { 28 | "localRoot": "${workspaceFolder}", 29 | "remoteRoot": "/usr/src/homeassistant" 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.3.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-yaml 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - repo: local 10 | hooks: 11 | - id: black 12 | name: black 13 | entry: black 14 | language: system 15 | types: [python] 16 | require_serial: true 17 | - id: flake8 18 | name: flake8 19 | entry: flake8 20 | language: system 21 | types: [python] 22 | require_serial: true 23 | - id: reorder-python-imports 24 | name: Reorder python imports 25 | entry: reorder-python-imports 26 | language: system 27 | types: [python] 28 | args: [--application-directories=custom_components] 29 | - repo: https://github.com/pre-commit/mirrors-prettier 30 | rev: v2.2.1 31 | hooks: 32 | - id: prettier 33 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/vscode-remote/devcontainer.json for format details. 2 | { 3 | "image": "ghcr.io/ludeeus/devcontainer/integration:latest", 4 | "name": "Waterkotte Heatpump integration development", 5 | "context": "..", 6 | "appPort": [ 7 | "9123:8123" 8 | ], 9 | "postCreateCommand": "container install", 10 | "extensions": [ 11 | "ms-python.python", 12 | "github.vscode-pull-request-github", 13 | "ryanluker.vscode-coverage-gutters", 14 | "ms-python.vscode-pylance" 15 | ], 16 | "settings": { 17 | "files.eol": "\n", 18 | "editor.tabSize": 4, 19 | "terminal.integrated.shell.linux": "/bin/bash", 20 | "python.pythonPath": "/usr/bin/python3", 21 | "python.analysis.autoSearchPaths": false, 22 | "python.linting.pylintEnabled": true, 23 | "python.linting.enabled": true, 24 | "python.formatting.provider": "black", 25 | "editor.formatOnPaste": false, 26 | "editor.formatOnSave": true, 27 | "editor.formatOnType": true, 28 | "files.trimTrailingWhitespace": true 29 | } 30 | } -------------------------------------------------------------------------------- /.gitea/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 15 | 16 | ## Version of the custom_component 17 | 18 | 21 | 22 | ## Configuration 23 | 24 | ```yaml 25 | Add your logs here. 26 | ``` 27 | 28 | ## Describe the bug 29 | 30 | A clear and concise description of what the bug is. 31 | 32 | ## Debug log 33 | 34 | 35 | 36 | ```text 37 | 38 | Add your logs here. 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 15 | 16 | ## Version of the custom_component 17 | 18 | 21 | 22 | ## Configuration 23 | 24 | ```yaml 25 | Add your logs here. 26 | ``` 27 | 28 | ## Describe the bug 29 | 30 | A clear and concise description of what the bug is. 31 | 32 | ## Debug log 33 | 34 | 35 | 36 | ```text 37 | 38 | Add your logs here. 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 pattisonmichael 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 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "title": "Waterkotte Heatpump", 6 | "description": "If you need help with the configuration have a look here: https://github.com/pattisonmichael/waterkotte-heatpump", 7 | "data": { 8 | "username": "Username", 9 | "password": "Password", 10 | "host": "Host or IP", 11 | "polling_interval": "Polling Interval in seconds", 12 | "system_type": "System Type" 13 | } 14 | } 15 | }, 16 | "error": { 17 | "auth": "Username/Password is wrong." 18 | }, 19 | "abort": { 20 | "single_instance_allowed": "Only a single instance is allowed." 21 | } 22 | }, 23 | "options": { 24 | "step": { 25 | "user": { 26 | "data": { 27 | "binary_sensor": "Binary sensor enabled", 28 | "sensor": "Sensor enabled", 29 | "switch": "Switch enabled", 30 | "select": "Select enabled", 31 | "username": "Username", 32 | "password": "Password", 33 | "host": "Host or IP", 34 | "polling_interval": "Polling Interval in seconds", 35 | "system_type": "System Type" 36 | } 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/entity.py: -------------------------------------------------------------------------------- 1 | """WaterkotteHeatpumpEntity class""" 2 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 3 | from homeassistant.helpers.entity import DeviceInfo 4 | # from homeassistant.config_entries import ConfigEntry 5 | # from .const import ATTRIBUTION 6 | from .const import DOMAIN 7 | # from .const import NAME 8 | # from .const import VERSION 9 | 10 | SENSOR_TYPES = [] 11 | 12 | 13 | class WaterkotteHeatpumpEntity(CoordinatorEntity): 14 | """ WaterkotteHeatpumpEntity Main common Class """ 15 | 16 | def __init__(self, coordinator, config_entry): 17 | self.config_entry = config_entry 18 | self._attr_device_info = DeviceInfo(identifiers={('DOMAIN', DOMAIN)}) 19 | super().__init__(coordinator) 20 | 21 | @property 22 | def unique_id(self): 23 | """Return a unique ID to use for this entity.""" 24 | return self.config_entry.data['serial'] 25 | # return self.config_entry.entry_id 26 | 27 | @property 28 | def has_entity_name(self) -> bool: 29 | """Return true.""" 30 | return True 31 | 32 | @property 33 | def device_state_attributes(self): 34 | """Return the state attributes.""" 35 | return { 36 | # "attribution": ATTRIBUTION, 37 | "id": str(self.coordinator.data.get("id")), 38 | "integration": DOMAIN, 39 | } 40 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build 3 | doctests = True 4 | # To work with Black 5 | max-line-length = 88 6 | # E501: line too long 7 | # W503: Line break occurred before a binary operator 8 | # E203: Whitespace before ':' 9 | # D202 No blank lines allowed after function docstring 10 | # W504 line break after binary operator 11 | ignore = 12 | E501, 13 | W503, 14 | E203, 15 | D202, 16 | W504 17 | 18 | [isort] 19 | # https://github.com/timothycrosley/isort 20 | # https://github.com/timothycrosley/isort/wiki/isort-Settings 21 | # splits long import on multiple lines indented by 4 spaces 22 | multi_line_output = 3 23 | include_trailing_comma=True 24 | force_grid_wrap=0 25 | use_parentheses=True 26 | line_length=88 27 | indent = " " 28 | # by default isort don't check module indexes 29 | not_skip = __init__.py 30 | # will group `import x` and `from x import` of the same module. 31 | force_sort_within_sections = true 32 | sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 33 | default_section = THIRDPARTY 34 | known_first_party = custom_components.waterkotte_heatpump, tests 35 | combine_as_imports = true 36 | 37 | [tool:pytest] 38 | addopts = -qq --cov=custom_components.waterkotte_heatpump 39 | console_output_style = count 40 | 41 | [coverage:run] 42 | branch = False 43 | 44 | [coverage:report] 45 | show_missing = true 46 | fail_under = 100 47 | -------------------------------------------------------------------------------- /.gitea/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | # pull_request event is required only for autolabeler 9 | pull_request: 10 | # Only following types are handled by the action, but one can default to all as well 11 | types: [opened, reopened, synchronize] 12 | # pull_request_target event is required for autolabeler to support PRs from forks 13 | # pull_request_target: 14 | # types: [opened, reopened, synchronize] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | update_release_draft: 21 | permissions: 22 | # write permission is required to create a github release 23 | contents: write 24 | # write permission is required for autolabeler 25 | # otherwise, read permission is required at least 26 | pull-requests: write 27 | runs-on: ubuntu-latest 28 | steps: 29 | # (Optional) GitHub Enterprise requires GHE_HOST variable set 30 | #- name: Set GHE_HOST 31 | # run: | 32 | # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV 33 | 34 | # Drafts your next Release notes as Pull Requests are merged into "master" 35 | - uses: release-drafter/release-drafter@v5 36 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 37 | # with: 38 | # config-name: my-config.yml 39 | # disable-autolabeler: true 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | # pull_request event is required only for autolabeler 9 | pull_request: 10 | # Only following types are handled by the action, but one can default to all as well 11 | types: [opened, reopened, synchronize] 12 | # pull_request_target event is required for autolabeler to support PRs from forks 13 | # pull_request_target: 14 | # types: [opened, reopened, synchronize] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | update_release_draft: 21 | permissions: 22 | # write permission is required to create a github release 23 | contents: write 24 | # write permission is required for autolabeler 25 | # otherwise, read permission is required at least 26 | pull-requests: write 27 | runs-on: ubuntu-latest 28 | steps: 29 | # (Optional) GitHub Enterprise requires GHE_HOST variable set 30 | #- name: Set GHE_HOST 31 | # run: | 32 | # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV 33 | 34 | # Drafts your next Release notes as Pull Requests are merged into "master" 35 | - uses: release-drafter/release-drafter@v5 36 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 37 | # with: 38 | # config-name: my-config.yml 39 | # disable-autolabeler: true 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Global fixtures for Waterkotte Heatpump integration.""" 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | 6 | pytest_plugins = "pytest_homeassistant_custom_component" 7 | 8 | 9 | # This fixture is used to prevent HomeAssistant from attempting to create and dismiss persistent 10 | # notifications. These calls would fail without this fixture since the persistent_notification 11 | # integration is never loaded during a test. 12 | @pytest.fixture(name="skip_notifications", autouse=True) 13 | def skip_notifications_fixture(): 14 | """Skip notification calls.""" 15 | with patch("homeassistant.components.persistent_notification.async_create"), patch( 16 | "homeassistant.components.persistent_notification.async_dismiss" 17 | ): 18 | yield 19 | 20 | 21 | # This fixture, when used, will result in calls to async_get_data to return None. To have the call 22 | # return a value, we would add the `return_value=` parameter to the patch call. 23 | @pytest.fixture(name="bypass_get_data") 24 | def bypass_get_data_fixture(): 25 | """Skip calls to get data from API.""" 26 | with patch("custom_components.waterkotte_heatpump.WaterkotteHeatpumpApiClient.async_get_data"): 27 | yield 28 | 29 | 30 | # In this fixture, we are forcing calls to async_get_data to raise an Exception. This is useful 31 | # for exception handling. 32 | @pytest.fixture(name="error_on_get_data") 33 | def error_get_data_fixture(): 34 | """Simulate error when retrieving data from API.""" 35 | with patch( 36 | "custom_components.waterkotte_heatpump.WaterkotteHeatpumpApiClient.async_get_data", 37 | side_effect=Exception, 38 | ): 39 | yield 40 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/const.py: -------------------------------------------------------------------------------- 1 | """Constants for Waterkotte Heatpump.""" 2 | # Base component constants 3 | NAME = "Waterkotte Heatpump" 4 | DOMAIN = "waterkotte_heatpump" 5 | DOMAIN_DATA = f"{DOMAIN}_data" 6 | VERSION = "0.1.00" 7 | TITLE = "Waterkotte" 8 | ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/" 9 | ISSUE_URL = "https://github.com/pattisonmichael/waterkotte-heatpump/issues" 10 | 11 | # Icons 12 | ICON = "mdi:format-quote-close" 13 | 14 | # Device classes 15 | BINARY_SENSOR_DEVICE_CLASS = "connectivity" 16 | DEVICE_CLASS_ENUM = "enum" 17 | 18 | # States 19 | STATE_AUTO = "auto" 20 | STATE_MANUAL = "manual" 21 | STATE_ON = "on" 22 | STATE_OFF = "off" 23 | # # #### Enum Options #### 24 | ENUM_ONOFFAUTO = [STATE_ON, STATE_OFF, STATE_AUTO] 25 | ENUM_OFFAUTOMANUAL = [STATE_OFF, STATE_AUTO, STATE_MANUAL] 26 | # Platforms 27 | BINARY_SENSOR = "binary_sensor" 28 | SENSOR = "sensor" 29 | SWITCH = "switch" 30 | SELECT = "select" 31 | NUMBER = "number" 32 | # TEXT = "text" 33 | # PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH, SELECT] 34 | PLATFORMS = [BINARY_SENSOR, SELECT, SENSOR, SWITCH, NUMBER] # 35 | SENSORS = ["heating", "cooling", "water"] 36 | 37 | 38 | # Configuration and options 39 | CONF_ENABLED = "enabled" 40 | CONF_USERNAME = "username" 41 | CONF_PASSWORD = "password" 42 | CONF_HOST = "host" 43 | CONF_POLLING_INTERVAL = "polling_interval" 44 | CONF_IP = "ip" 45 | CONF_BIOS = "bios" 46 | CONF_FW = "fw" 47 | CONF_SERIAL = "serial" 48 | CONF_SERIES = "series" 49 | CONF_ID = "id" 50 | CONF_SYSTEMTYPE = "system_type" 51 | # Defaults 52 | DEFAULT_NAME = DOMAIN 53 | 54 | 55 | STARTUP_MESSAGE = f""" 56 | ------------------------------------------------------------------- 57 | {NAME} 58 | Version: {VERSION} 59 | This is a custom integration! 60 | If you have any issues with this you need to open an issue here: 61 | {ISSUE_URL} 62 | ------------------------------------------------------------------- 63 | """ 64 | -------------------------------------------------------------------------------- /.gitea/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Labels names are important as they are used by Release Drafter to decide 3 | # regarding where to record them in changelog or if to skip them. 4 | # 5 | # The repository labels will be automatically configured using this file and 6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler. 7 | - name: breaking 8 | description: Breaking Changes 9 | color: bfd4f2 10 | - name: bug 11 | description: Something isn't working 12 | color: d73a4a 13 | - name: build 14 | description: Build System and Dependencies 15 | color: bfdadc 16 | - name: ci 17 | description: Continuous Integration 18 | color: 4a97d6 19 | - name: dependencies 20 | description: Pull requests that update a dependency file 21 | color: 0366d6 22 | - name: documentation 23 | description: Improvements or additions to documentation 24 | color: 0075ca 25 | - name: duplicate 26 | description: This issue or pull request already exists 27 | color: cfd3d7 28 | - name: enhancement 29 | description: New feature or request 30 | color: a2eeef 31 | - name: github_actions 32 | description: Pull requests that update Github_actions code 33 | color: "000000" 34 | - name: good first issue 35 | description: Good for newcomers 36 | color: 7057ff 37 | - name: help wanted 38 | description: Extra attention is needed 39 | color: 008672 40 | - name: invalid 41 | description: This doesn't seem right 42 | color: e4e669 43 | - name: performance 44 | description: Performance 45 | color: "016175" 46 | - name: python 47 | description: Pull requests that update Python code 48 | color: 2b67c6 49 | - name: question 50 | description: Further information is requested 51 | color: d876e3 52 | - name: refactoring 53 | description: Refactoring 54 | color: ef67c4 55 | - name: removal 56 | description: Removals and Deprecations 57 | color: 9ae7ea 58 | - name: style 59 | description: Style 60 | color: c120e5 61 | - name: testing 62 | description: Testing 63 | color: b1fc6f 64 | - name: wontfix 65 | description: This will not be worked on 66 | color: ffffff 67 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Labels names are important as they are used by Release Drafter to decide 3 | # regarding where to record them in changelog or if to skip them. 4 | # 5 | # The repository labels will be automatically configured using this file and 6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler. 7 | - name: breaking 8 | description: Breaking Changes 9 | color: bfd4f2 10 | - name: bug 11 | description: Something isn't working 12 | color: d73a4a 13 | - name: build 14 | description: Build System and Dependencies 15 | color: bfdadc 16 | - name: ci 17 | description: Continuous Integration 18 | color: 4a97d6 19 | - name: dependencies 20 | description: Pull requests that update a dependency file 21 | color: 0366d6 22 | - name: documentation 23 | description: Improvements or additions to documentation 24 | color: 0075ca 25 | - name: duplicate 26 | description: This issue or pull request already exists 27 | color: cfd3d7 28 | - name: enhancement 29 | description: New feature or request 30 | color: a2eeef 31 | - name: github_actions 32 | description: Pull requests that update Github_actions code 33 | color: "000000" 34 | - name: good first issue 35 | description: Good for newcomers 36 | color: 7057ff 37 | - name: help wanted 38 | description: Extra attention is needed 39 | color: 008672 40 | - name: invalid 41 | description: This doesn't seem right 42 | color: e4e669 43 | - name: performance 44 | description: Performance 45 | color: "016175" 46 | - name: python 47 | description: Pull requests that update Python code 48 | color: 2b67c6 49 | - name: question 50 | description: Further information is requested 51 | color: d876e3 52 | - name: refactoring 53 | description: Refactoring 54 | color: ef67c4 55 | - name: removal 56 | description: Removals and Deprecations 57 | color: 9ae7ea 58 | - name: style 59 | description: Style 60 | color: c120e5 61 | - name: testing 62 | description: Testing 63 | color: b1fc6f 64 | - name: wontfix 65 | description: This will not be worked on 66 | color: ffffff 67 | -------------------------------------------------------------------------------- /tests/test_switch.py: -------------------------------------------------------------------------------- 1 | """Test Waterkotte Heatpump switch.""" 2 | from unittest.mock import call 3 | from unittest.mock import patch 4 | 5 | from custom_components.waterkotte_heatpump import ( 6 | async_setup_entry, 7 | ) 8 | from custom_components.waterkotte_heatpump.const import ( 9 | DEFAULT_NAME, 10 | ) 11 | from custom_components.waterkotte_heatpump.const import ( 12 | DOMAIN, 13 | ) 14 | from custom_components.waterkotte_heatpump.const import ( 15 | SWITCH, 16 | ) 17 | from homeassistant.components.switch import SERVICE_TURN_OFF 18 | from homeassistant.components.switch import SERVICE_TURN_ON 19 | from homeassistant.const import ATTR_ENTITY_ID 20 | from pytest_homeassistant_custom_component.common import MockConfigEntry 21 | 22 | from .const import MOCK_CONFIG 23 | 24 | 25 | async def test_switch_services(hass): 26 | """Test switch services.""" 27 | # Create a mock entry so we don't have to go through config flow 28 | config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") 29 | assert await async_setup_entry(hass, config_entry) 30 | await hass.async_block_till_done() 31 | 32 | # Functions/objects can be patched directly in test code as well and can be used to test 33 | # additional things, like whether a function was called or what arguments it was called with 34 | with patch( 35 | "custom_components.waterkotte_heatpump.WaterkotteHeatpumpApiClient.async_set_title" 36 | ) as title_func: 37 | await hass.services.async_call( 38 | SWITCH, 39 | SERVICE_TURN_OFF, 40 | service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, 41 | blocking=True, 42 | ) 43 | assert title_func.called 44 | assert title_func.call_args == call("foo") 45 | 46 | title_func.reset_mock() 47 | 48 | await hass.services.async_call( 49 | SWITCH, 50 | SERVICE_TURN_ON, 51 | service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, 52 | blocking=True, 53 | ) 54 | assert title_func.called 55 | assert title_func.call_args == call("bar") 56 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/services.yaml: -------------------------------------------------------------------------------- 1 | # Service ID 2 | set_holiday: 3 | # Service name as shown in UI 4 | name: Set Holiday 5 | # Description of the service 6 | description: Sets start and end times for holiday mode. 7 | # If the service accepts entity IDs, target allows the user to specify entities by entity, device, or area. If `target` is specified, `entity_id` should not be defined in the `fields` map. By default it shows only targets matching entities from the same domain as the service, but if further customization is required, target supports the entity, device, and area selectors (https://www.home-assistant.io/docs/blueprint/selectors/). Entity selector parameters will automatically be applied to device and area, and device selector parameters will automatically be applied to area. 8 | #target: 9 | # Different fields that your service accepts 10 | fields: 11 | # Key of the field 12 | start: 13 | # Field name as shown in UI 14 | name: Start Time & Date 15 | # Description of the field 16 | description: Set the beginning of the holiday 17 | # Whether or not field is required (default = false) 18 | required: true 19 | # Advanced fields are only shown when the advanced mode is enabled for the user (default = false) 20 | # advanced: true 21 | # Example value that can be passed for this field 22 | #example: "low" 23 | # The default field value 24 | #default: "high" 25 | selector: 26 | datetime: 27 | # Selector (https://www.home-assistant.io/docs/blueprint/selectors/) to control the input UI for this field 28 | #selector: 29 | # select: 30 | # options: 31 | # - "off" 32 | # - "low" 33 | # - "medium" 34 | # - "high" 35 | 36 | end: 37 | name: End Time & Date 38 | description: Set the end of the holiday 39 | required: true 40 | selector: 41 | datetime: 42 | 43 | get_energy_balance: 44 | # Service name as shown in UI 45 | name: Get Current Year Energy Balance 46 | # Description of the service 47 | description: Get the energy balance by different usage for the current year 48 | get_energy_balance_monthly: 49 | # Service name as shown in UI 50 | name: Get rolling 12 Month breakdown 51 | # Description of the service 52 | description: Gets the energy balance breakdown per month in a rolling 12 month window -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | [![GitHub Release][releases-shield]][releases] 2 | [![GitHub Activity][commits-shield]][commits] 3 | [![License][license-shield]][license] 4 | 5 | [![hacs][hacsbadge]][hacs] 6 | [![Project Maintenance][maintenance-shield]][user_profile] 7 | [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] 8 | 9 | [![Discord][discord-shield]][discord] 10 | [![Community Forum][forum-shield]][forum] 11 | 12 | **This component will set up the following platforms.** 13 | 14 | | Platform | Description | 15 | | --------------- | ----------------------------------- | 16 | | `binary_sensor` | Show something `True` or `False`. | 17 | | `sensor` | Show info from API. | 18 | | `switch` | Switch something `True` or `False`. | 19 | 20 | ![example][exampleimg] 21 | 22 | {% if not installed %} 23 | 24 | ## Installation 25 | 26 | 1. Click install. 27 | 1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Waterkotte Heatpump". 28 | 29 | {% endif %} 30 | 31 | ## Configuration is done in the UI 32 | 33 | 34 | 35 | ## Credits 36 | 37 | This project was generated from [@oncleben31](https://github.com/oncleben31)'s [Home Assistant Custom Component Cookiecutter](https://github.com/oncleben31/cookiecutter-homeassistant-custom-component) template. 38 | 39 | Code template was mainly taken from [@Ludeeus](https://github.com/ludeeus)'s [integration_blueprint][integration_blueprint] template 40 | 41 | --- 42 | 43 | [integration_blueprint]: https://github.com/custom-components/integration_blueprint 44 | [buymecoffee]: https://www.buymeacoffee.com/ludeeus 45 | [buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge 46 | [commits-shield]: https://img.shields.io/github/commit-activity/y/pattisonmichael/waterkotte-integration.svg?style=for-the-badge 47 | [commits]: https://github.com/pattisonmichael/waterkotte-integration/commits/main 48 | [hacs]: https://hacs.xyz 49 | [hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge 50 | [discord]: https://discord.gg/Qa5fW2R 51 | [discord-shield]: https://img.shields.io/discord/330944238910963714.svg?style=for-the-badge 52 | [exampleimg]: example.png 53 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge 54 | [forum]: https://community.home-assistant.io/ 55 | [license]: https://github.com/pattisonmichael/waterkotte-integration/blob/main/LICENSE 56 | [license-shield]: https://img.shields.io/github/license/pattisonmichael/waterkotte-integration.svg?style=for-the-badge 57 | [maintenance-shield]: https://img.shields.io/badge/maintainer-%40pattisonmichael-blue.svg?style=for-the-badge 58 | [releases-shield]: https://img.shields.io/github/release/pattisonmichael/waterkotte-integration.svg?style=for-the-badge 59 | [releases]: https://github.com/pattisonmichael/waterkotte-integration/releases 60 | [user_profile]: https://github.com/pattisonmichael 61 | -------------------------------------------------------------------------------- /tests/test_init.py: -------------------------------------------------------------------------------- 1 | """Test Waterkotte Heatpump setup process.""" 2 | import pytest 3 | from custom_components.waterkotte_heatpump import ( 4 | async_reload_entry, 5 | ) 6 | from custom_components.waterkotte_heatpump import ( 7 | async_setup_entry, 8 | ) 9 | from custom_components.waterkotte_heatpump import ( 10 | async_unload_entry, 11 | ) 12 | from custom_components.waterkotte_heatpump import ( 13 | WaterkotteHeatpumpDataUpdateCoordinator, 14 | ) 15 | from custom_components.waterkotte_heatpump.const import ( 16 | DOMAIN, 17 | ) 18 | from homeassistant.exceptions import ConfigEntryNotReady 19 | from pytest_homeassistant_custom_component.common import MockConfigEntry 20 | 21 | from .const import MOCK_CONFIG 22 | 23 | 24 | # We can pass fixtures as defined in conftest.py to tell pytest to use the fixture 25 | # for a given test. We can also leverage fixtures and mocks that are available in 26 | # Home Assistant using the pytest_homeassistant_custom_component plugin. 27 | # Assertions allow you to verify that the return value of whatever is on the left 28 | # side of the assertion matches with the right side. 29 | async def test_setup_unload_and_reload_entry(hass, bypass_get_data): 30 | """Test entry setup and unload.""" 31 | # Create a mock entry so we don't have to go through config flow 32 | config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") 33 | 34 | # Set up the entry and assert that the values set during setup are where we expect 35 | # them to be. Because we have patched the WaterkotteHeatpumpDataUpdateCoordinator.async_get_data 36 | # call, no code from custom_components/waterkotte_heatpump/api.py actually runs. 37 | assert await async_setup_entry(hass, config_entry) 38 | assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] 39 | assert ( 40 | type(hass.data[DOMAIN][config_entry.entry_id]) == WaterkotteHeatpumpDataUpdateCoordinator 41 | ) 42 | 43 | # Reload the entry and assert that the data from above is still there 44 | assert await async_reload_entry(hass, config_entry) is None 45 | assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] 46 | assert ( 47 | type(hass.data[DOMAIN][config_entry.entry_id]) == WaterkotteHeatpumpDataUpdateCoordinator 48 | ) 49 | 50 | # Unload the entry and verify that the data has been removed 51 | assert await async_unload_entry(hass, config_entry) 52 | assert config_entry.entry_id not in hass.data[DOMAIN] 53 | 54 | 55 | async def test_setup_entry_exception(hass, error_on_get_data): 56 | """Test ConfigEntryNotReady when API raises an exception during entry setup.""" 57 | config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") 58 | 59 | # In this case we are testing the condition where async_setup_entry raises 60 | # ConfigEntryNotReady using the `error_on_get_data` fixture which simulates 61 | # an error. 62 | with pytest.raises(ConfigEntryNotReady): 63 | assert await async_setup_entry(hass, config_entry) 64 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | """Tests for Waterkotte Heatpump api.""" 2 | import asyncio 3 | 4 | import aiohttp 5 | from custom_components.waterkotte_heatpump.api import ( 6 | WaterkotteHeatpumpApiClient, 7 | ) 8 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 9 | 10 | 11 | async def test_api(hass, aioclient_mock, caplog): 12 | """Test API calls.""" 13 | 14 | # To test the api submodule, we first create an instance of our API client 15 | api = WaterkotteHeatpumpApiClient("test", "test", async_get_clientsession(hass)) 16 | 17 | # Use aioclient_mock which is provided by `pytest_homeassistant_custom_components` 18 | # to mock responses to aiohttp requests. In this case we are telling the mock to 19 | # return {"test": "test"} when a `GET` call is made to the specified URL. We then 20 | # call `async_get_data` which will make that `GET` request. 21 | aioclient_mock.get( 22 | "https://jsonplaceholder.typicode.com/posts/1", json={"test": "test"} 23 | ) 24 | assert await api.async_get_data() == {"test": "test"} 25 | 26 | # We do the same for `async_set_title`. Note the difference in the mock call 27 | # between the previous step and this one. We use `patch` here instead of `get` 28 | # because we know that `async_set_title` calls `api_wrapper` with `patch` as the 29 | # first parameter 30 | aioclient_mock.patch("https://jsonplaceholder.typicode.com/posts/1") 31 | assert await api.async_set_title("test") is None 32 | 33 | # In order to get 100% coverage, we need to test `api_wrapper` to test the code 34 | # that isn't already called by `async_get_data` and `async_set_title`. Because the 35 | # only logic that lives inside `api_wrapper` that is not being handled by a third 36 | # party library (aiohttp) is the exception handling, we also want to simulate 37 | # raising the exceptions to ensure that the function handles them as expected. 38 | # The caplog fixture allows access to log messages in tests. This is particularly 39 | # useful during exception handling testing since often the only action as part of 40 | # exception handling is a logging statement 41 | caplog.clear() 42 | aioclient_mock.put( 43 | "https://jsonplaceholder.typicode.com/posts/1", exc=asyncio.TimeoutError 44 | ) 45 | assert ( 46 | await api.api_wrapper("put", "https://jsonplaceholder.typicode.com/posts/1") 47 | is None 48 | ) 49 | assert ( 50 | len(caplog.record_tuples) == 1 51 | and "Timeout error fetching information from" in caplog.record_tuples[0][2] 52 | ) 53 | 54 | caplog.clear() 55 | aioclient_mock.post( 56 | "https://jsonplaceholder.typicode.com/posts/1", exc=aiohttp.ClientError 57 | ) 58 | assert ( 59 | await api.api_wrapper("post", "https://jsonplaceholder.typicode.com/posts/1") 60 | is None 61 | ) 62 | assert ( 63 | len(caplog.record_tuples) == 1 64 | and "Error fetching information from" in caplog.record_tuples[0][2] 65 | ) 66 | 67 | caplog.clear() 68 | aioclient_mock.post("https://jsonplaceholder.typicode.com/posts/2", exc=Exception) 69 | assert ( 70 | await api.api_wrapper("post", "https://jsonplaceholder.typicode.com/posts/2") 71 | is None 72 | ) 73 | assert ( 74 | len(caplog.record_tuples) == 1 75 | and "Something really wrong happened!" in caplog.record_tuples[0][2] 76 | ) 77 | 78 | caplog.clear() 79 | aioclient_mock.post("https://jsonplaceholder.typicode.com/posts/3", exc=TypeError) 80 | assert ( 81 | await api.api_wrapper("post", "https://jsonplaceholder.typicode.com/posts/3") 82 | is None 83 | ) 84 | assert ( 85 | len(caplog.record_tuples) == 1 86 | and "Error parsing information from" in caplog.record_tuples[0][2] 87 | ) 88 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | Contributing to this project should be as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | 10 | ## Github is used for everything 11 | 12 | Github is used to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | Pull requests are the best way to propose changes to the codebase. 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 2. If you've changed something, update the documentation. 18 | 3. Make sure your code lints (using black). 19 | 4. Test you contribution. 20 | 5. Issue that pull request! 21 | 22 | ## Any contributions you make will be under the MIT Software License 23 | 24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using Github's [issues](../../issues) 27 | 28 | GitHub issues are used to track public bugs. 29 | Report a bug by [opening a new issue](../../issues/new/choose); it's that easy! 30 | 31 | ## Write bug reports with detail, background, and sample code 32 | 33 | **Great Bug Reports** tend to have: 34 | 35 | - A quick summary and/or background 36 | - Steps to reproduce 37 | - Be specific! 38 | - Give sample code if you can. 39 | - What you expected would happen 40 | - What actually happens 41 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 42 | 43 | People _love_ thorough bug reports. I'm not even kidding. 44 | 45 | ## Use a Consistent Coding Style 46 | 47 | Use [black](https://github.com/ambv/black) and [prettier](https://prettier.io/) 48 | to make sure the code follows the style. 49 | 50 | Or use the `pre-commit` settings implemented in this repository 51 | (see deicated section below). 52 | 53 | ## Test your code modification 54 | 55 | This custom component is based on [integration_blueprint template](https://github.com/custom-components/integration_blueprint). 56 | 57 | It comes with development environment in a container, easy to launch 58 | if you use Visual Studio Code. With this container you will have a stand alone 59 | Home Assistant instance running and already configured with the included 60 | [`.devcontainer/configuration.yaml`](./.devcontainer/configuration.yaml) 61 | file. 62 | 63 | You can use the `pre-commit` settings implemented in this repository to have 64 | linting tool checking your contributions (see deicated section below). 65 | 66 | You should also verify that existing [tests](./tests) are still working 67 | and you are encouraged to add new ones. 68 | You can run the tests using the following commands from the root folder: 69 | 70 | ```bash 71 | # Create a virtual environment 72 | python3 -m venv venv 73 | source venv/bin/activate 74 | # Install requirements 75 | pip install -r requirements_test.txt 76 | # Run tests and get a summary of successes/failures and code coverage 77 | pytest --durations=10 --cov-report term-missing --cov=custom_components.waterkotte_heatpump tests 78 | ``` 79 | 80 | If any of the tests fail, make the necessary changes to the tests as part of 81 | your changes to the integration. 82 | 83 | ## Pre-commit 84 | 85 | You can use the [pre-commit](https://pre-commit.com/) settings included in the 86 | repostory to have code style and linting checks. 87 | 88 | With `pre-commit` tool already installed, 89 | activate the settings of the repository: 90 | 91 | ```console 92 | $ pre-commit install 93 | ``` 94 | 95 | Now the pre-commit tests will be done every time you commit. 96 | 97 | You can run the tests on all repository file with the command: 98 | 99 | ```console 100 | $ pre-commit run --all-files 101 | ``` 102 | 103 | ## License 104 | 105 | By contributing, you agree that your contributions will be licensed under its MIT License. 106 | -------------------------------------------------------------------------------- /tests/test_config_flow.py: -------------------------------------------------------------------------------- 1 | """Test Waterkotte Heatpump config flow.""" 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | from custom_components.waterkotte_heatpump.const import ( 6 | BINARY_SENSOR, 7 | ) 8 | from custom_components.waterkotte_heatpump.const import ( 9 | DOMAIN, 10 | ) 11 | from custom_components.waterkotte_heatpump.const import ( 12 | PLATFORMS, 13 | ) 14 | from custom_components.waterkotte_heatpump.const import ( 15 | SENSOR, 16 | ) 17 | from custom_components.waterkotte_heatpump.const import ( 18 | SWITCH, 19 | ) 20 | from homeassistant import config_entries 21 | from homeassistant import data_entry_flow 22 | from pytest_homeassistant_custom_component.common import MockConfigEntry 23 | 24 | from .const import MOCK_CONFIG 25 | 26 | 27 | # This fixture bypasses the actual setup of the integration 28 | # since we only want to test the config flow. We test the 29 | # actual functionality of the integration in other test modules. 30 | @pytest.fixture(autouse=True) 31 | def bypass_setup_fixture(): 32 | """Prevent setup.""" 33 | with patch("custom_components.waterkotte_heatpump.async_setup", return_value=True,), patch( 34 | "custom_components.waterkotte_heatpump.async_setup_entry", 35 | return_value=True, 36 | ): 37 | yield 38 | 39 | 40 | # Here we simiulate a successful config flow from the backend. 41 | # Note that we use the `bypass_get_data` fixture here because 42 | # we want the config flow validation to succeed during the test. 43 | async def test_successful_config_flow(hass, bypass_get_data): 44 | """Test a successful config flow.""" 45 | # Initialize a config flow 46 | result = await hass.config_entries.flow.async_init( 47 | DOMAIN, context={"source": config_entries.SOURCE_USER} 48 | ) 49 | 50 | # Check that the config flow shows the user form as the first step 51 | assert result["type"] == data_entry_flow.RESULT_TYPE_FORM 52 | assert result["step_id"] == "user" 53 | 54 | # If a user were to enter `test_username` for username and `test_password` 55 | # for password, it would result in this function call 56 | result = await hass.config_entries.flow.async_configure( 57 | result["flow_id"], user_input=MOCK_CONFIG 58 | ) 59 | 60 | # Check that the config flow is complete and a new entry is created with 61 | # the input data 62 | assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY 63 | assert result["title"] == "test_username" 64 | assert result["data"] == MOCK_CONFIG 65 | assert result["result"] 66 | 67 | 68 | # In this case, we want to simulate a failure during the config flow. 69 | # We use the `error_on_get_data` mock instead of `bypass_get_data` 70 | # (note the function parameters) to raise an Exception during 71 | # validation of the input config. 72 | async def test_failed_config_flow(hass, error_on_get_data): 73 | """Test a failed config flow due to credential validation failure.""" 74 | 75 | result = await hass.config_entries.flow.async_init( 76 | DOMAIN, context={"source": config_entries.SOURCE_USER} 77 | ) 78 | 79 | assert result["type"] == data_entry_flow.RESULT_TYPE_FORM 80 | assert result["step_id"] == "user" 81 | 82 | result = await hass.config_entries.flow.async_configure( 83 | result["flow_id"], user_input=MOCK_CONFIG 84 | ) 85 | 86 | assert result["type"] == data_entry_flow.RESULT_TYPE_FORM 87 | assert result["errors"] == {"base": "auth"} 88 | 89 | 90 | # Our config flow also has an options flow, so we must test it as well. 91 | async def test_options_flow(hass): 92 | """Test an options flow.""" 93 | # Create a new MockConfigEntry and add to HASS (we're bypassing config 94 | # flow entirely) 95 | entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") 96 | entry.add_to_hass(hass) 97 | 98 | # Initialize an options flow 99 | await hass.config_entries.async_setup(entry.entry_id) 100 | result = await hass.config_entries.options.async_init(entry.entry_id) 101 | 102 | # Verify that the first options step is a user form 103 | assert result["type"] == data_entry_flow.RESULT_TYPE_FORM 104 | assert result["step_id"] == "user" 105 | 106 | # Enter some fake data into the form 107 | result = await hass.config_entries.options.async_configure( 108 | result["flow_id"], 109 | user_input={platform: platform != SENSOR for platform in PLATFORMS}, 110 | ) 111 | 112 | # Verify that the flow finishes 113 | assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY 114 | assert result["title"] == "test_username" 115 | 116 | # Verify that the options were updated 117 | assert entry.options == {BINARY_SENSOR: True, SENSOR: False, SWITCH: True} 118 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/api.py: -------------------------------------------------------------------------------- 1 | """Sample API Client.""" 2 | from typing import Sequence 3 | import asyncio 4 | import logging 5 | import socket 6 | 7 | # import re 8 | import aiohttp 9 | import async_timeout 10 | from pywaterkotte.ecotouch import Ecotouch, EcotouchTag 11 | from pywaterkotte.easycon import Easycon 12 | from pywaterkotte.detect import EASYCON, ECOTOUCH 13 | 14 | TIMEOUT = 10 15 | 16 | 17 | _LOGGER: logging.Logger = logging.getLogger(__package__) 18 | 19 | HEADERS = {"Content-type": "application/json; charset=UTF-8"} 20 | 21 | 22 | class WaterkotteHeatpumpApiClient: 23 | """Waterkotte Heatpump API Client Class""" 24 | 25 | def __init__( 26 | self, 27 | host: str, 28 | username: str, 29 | password: str, 30 | session: aiohttp.ClientSession, 31 | tags: str, 32 | systemType: str, 33 | ) -> None: 34 | """Sample API Client.""" 35 | self._username = username 36 | self._password = password 37 | self._session = session 38 | self._host = host 39 | # self._hass = hass 40 | self._systemType = systemType 41 | if systemType == ECOTOUCH: 42 | self._client = Ecotouch(host,_LOGGER) 43 | elif systemType == EASYCON: 44 | self._client = Easycon(host,_LOGGER) 45 | else: 46 | print("Error unknown System type!") 47 | # self._entities = [] 48 | self.tags = tags 49 | 50 | # # session = async_create_clientsession(self.hass) 51 | # client = Ecotouch(host) 52 | # await client.login(username, password) 53 | # ret = await client.read_value(EcotouchTag.DATE_DAY) 54 | # # print(ret) 55 | # return ret["status"] == "E_OK" 56 | # # await client.async_get_data() 57 | 58 | @property 59 | def tags(self): 60 | """getter for Tags""" 61 | return self.__tags 62 | 63 | @tags.setter 64 | def tags(self, tags): 65 | self.__tags = tags 66 | 67 | async def login(self) -> None: 68 | """Login to the API.""" 69 | if self._client.auth_cookies is None: 70 | try: 71 | await self._client.login(self._username, self._password) 72 | except Exception as exception: # pylint: disable=broad-except 73 | print(exception) 74 | await self._client.logout() 75 | await self._client.login(self._username, self._password) 76 | 77 | # return ret 78 | 79 | async def async_get_data(self) -> dict: 80 | """Get data from the API.""" 81 | 82 | ret = await self._client.read_values(self.tags) 83 | return ret 84 | 85 | async def async_read_values(self, tags: Sequence[EcotouchTag]) -> dict: 86 | """Get data from the API.""" 87 | ret = await self._client.read_values(tags) 88 | return ret 89 | 90 | async def async_read_value(self, tag: EcotouchTag) -> dict: 91 | """Get data from the API.""" 92 | ret = await self._client.read_value(tag) 93 | return ret 94 | 95 | async def async_write_value(self, tag: EcotouchTag, value): 96 | """Write data to API""" 97 | res = await self._client.write_value(tag, value) 98 | return res 99 | # if res is not None: 100 | # self.tags[tag] = res 101 | 102 | async def async_set_title(self, value: str) -> None: 103 | """Get data from the API.""" 104 | url = "https://jsonplaceholder.typicode.com/posts/1" 105 | await self.api_wrapper("patch", url, data={"title": value}, headers=HEADERS) 106 | 107 | async def api_wrapper( 108 | # self, method: str, url: str, data: dict = {}, headers: dict = {} 109 | self, 110 | method: str, 111 | url: str, 112 | data=None, 113 | headers=None, 114 | ) -> dict: 115 | """Get information from the API.""" 116 | if data is None: 117 | data = {} 118 | if headers is None: 119 | headers = {} 120 | try: 121 | async with async_timeout.timeout( # pylint: disable=unexpected-keyword-arg 122 | TIMEOUT, loop=asyncio.get_event_loop() 123 | ): 124 | # async with async_timeout.timeout(TIMEOUT): 125 | if method == "get": 126 | response = await self._session.get(url, headers=headers) 127 | return await response.json() 128 | 129 | elif method == "put": 130 | await self._session.put(url, headers=headers, json=data) 131 | 132 | elif method == "patch": 133 | await self._session.patch(url, headers=headers, json=data) 134 | 135 | elif method == "post": 136 | await self._session.post(url, headers=headers, json=data) 137 | 138 | except asyncio.TimeoutError as exception: 139 | _LOGGER.error( 140 | "Timeout error fetching information from %s - %s", 141 | url, 142 | exception, 143 | ) 144 | 145 | except (KeyError, TypeError) as exception: 146 | _LOGGER.error( 147 | "Error parsing information from %s - %s", 148 | url, 149 | exception, 150 | ) 151 | except (aiohttp.ClientError, socket.gaierror) as exception: 152 | _LOGGER.error( 153 | "Error fetching information from %s - %s", 154 | url, 155 | exception, 156 | ) 157 | except Exception as exception: # pylint: disable=broad-except 158 | _LOGGER.error("Something really wrong happened! - %s", exception) 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Waterkotte Heatpump 2 | 3 | [![GitHub Release][releases-shield]][releases] 4 | [![GitHub Activity][commits-shield]][commits] 5 | [![License][license-shield]](LICENSE) 6 | 7 | [![pre-commit][pre-commit-shield]][pre-commit] 8 | [![Black][black-shield]][black] 9 | 10 | [![hacs][hacsbadge]][hacs] 11 | [![Project Maintenance][maintenance-shield]][user_profile] 12 | [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] 13 | 14 | [![Discord][discord-shield]][discord] 15 | [![Community Forum][forum-shield]][forum] 16 | 17 | # Waterkotte Heatpump Integration for Home Assistant 18 | **This component will set up the following platforms.** 19 | 20 | | Platform | Description | 21 | | --------------- | --------------------------------------- | 22 | | `binary_sensor` | Show something `True` or `False`. | 23 | | `sensor` | Show info from Waterkotte Heatpump API. | 24 | | `switch` | Switch something `True` or `False`. | 25 | | `select` | Select a value from options. | 26 | | `number` | Change a numeric value. | 27 | | `service` | Provides services to interact with heatpump | 28 | 29 | ![logo][logoimg] 30 | 31 | ## Installation 32 | ## Installation 33 | ### HACS [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs) 34 | 1. Add a custom integration repository to HACS: [waterkotte-integration](https://github.com/pattisonmichael/waterkotte-integration) 35 | 1. Install the custom integration 36 | 1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Waterkotte Heatpump" 37 | 1. Setup the waterkotte custom integration as described below 38 | 39 | ### Manual 40 | 1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). 41 | 2. If you do not have a `custom_components` directory (folder) there, you need to create it. 42 | 3. In the `custom_components` directory (folder) create a new folder called `waterkotte_heatpump`. 43 | 4. Download _all_ the files from the `custom_components/waterkotte_heatpump/` directory (folder) in this repository. 44 | 5. Place the files you downloaded in the new directory (folder) you created. 45 | 6. Restart Home Assistant 46 | 7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Waterkotte Heatpump" 47 | 48 | ## Configuration is done in the UI 49 | 50 | Use the Config flow to add the integration. You will need the IP/Hostname and Username/Password to log in. 51 | 52 | 53 | Not all available sensors are enabled by default. 54 | 55 | To set the the times for the holiday mode use the provided service `waterkotte_heatpump.set_holiday` and set `start` and `end` parameter. 56 | 57 | ## Services 58 | 59 | The Integration provides currently 3 services: 60 | - set_holiday 61 | Allows to set the start and end datetimes for the holiday mode 62 | 63 | - get_energy_balance 64 | Retrieves the overall energy consumption data for the year 65 | 66 | - get_energy_balance_monthly 67 | Retrieves the monthly breakdown energy consumption data for a moving 12 month window. 68 | 1 = January, 2 = February, etc... 69 | 70 | ## Troubleshooting 71 | 72 | 73 | ### Sessions 74 | 75 | The Heatpump only allows 2 sessions and there is not way to close a session. Sometimes you will get an error about the login. Just wait a few minutes and it should auto correct itself. Session usually time out within about 5 min. 76 | 77 | ### Stale Data 78 | 79 | The Heatpump will not always respond with data. This happens usually after the system changes status, e.g. start/stop the heating. There is not much we can do about this unfortunately. I try to cache the data in possible for a better UX. 80 | 81 | ## Credits 82 | 83 | This project was generated from [@oncleben31](https://github.com/oncleben31)'s [Home Assistant Custom Component Cookiecutter](https://github.com/oncleben31/cookiecutter-homeassistant-custom-component) template. 84 | 85 | Code template was mainly taken from [@Ludeeus](https://github.com/ludeeus)'s [integration_blueprint][integration_blueprint] template 86 | 87 | --- 88 | 89 | [integration_blueprint]: https://github.com/custom-components/integration_blueprint 90 | [black]: https://github.com/psf/black 91 | [black-shield]: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge 92 | [buymecoffee]: https://www.buymeacoffee.com/pattisonmichael 93 | [buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge 94 | [commits-shield]: https://img.shields.io/github/commit-activity/y/pattisonmichael/waterkotte-integration.svg?style=for-the-badge 95 | [commits]: https://github.com/pattisonmichael/waterkotte-integration/commits/main 96 | [hacs]: https://hacs.xyz 97 | [hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge 98 | [discord]: https://discord.gg/Qa5fW2R 99 | [discord-shield]: https://img.shields.io/discord/330944238910963714.svg?style=for-the-badge 100 | [logoimg]: logo.png 101 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge 102 | [forum]: https://community.home-assistant.io/ 103 | [license-shield]: https://img.shields.io/github/license/pattisonmichael/waterkotte-integration.svg?style=for-the-badge 104 | [maintenance-shield]: https://img.shields.io/badge/maintainer-%40pattisonmichael-blue.svg?style=for-the-badge 105 | [pre-commit]: https://github.com/pre-commit/pre-commit 106 | [pre-commit-shield]: https://img.shields.io/badge/pre--commit-enabled-brightgreen?style=for-the-badge 107 | [releases-shield]: https://img.shields.io/github/release/pattisonmichael/waterkotte-integration.svg?style=for-the-badge 108 | [releases]: https://github.com/pattisonmichael/waterkotte-integration/releases 109 | [user_profile]: https://github.com/pattisonmichael 110 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/switch.py: -------------------------------------------------------------------------------- 1 | """Switch platform for Waterkotte Heatpump.""" 2 | import logging 3 | from homeassistant.components.switch import SwitchEntity 4 | 5 | from pywaterkotte.ecotouch import EcotouchTag 6 | from .const import DOMAIN 7 | 8 | from .entity import WaterkotteHeatpumpEntity 9 | 10 | # from .const import DOMAIN # , NAME, CONF_FW, CONF_BIOS, CONF_IP 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | # Sensor types are defined as: 16 | # variable -> [0]title, [1] EcoTouchTag, [2]device_class, [3]units, [4]icon, [5]enabled_by_default, [6]options, [7]entity_category #pylint: disable=line-too-long 17 | SENSOR_TYPES = { 18 | "holiday_enabled": [ 19 | "Holiday Mode", 20 | EcotouchTag.HOLIDAY_ENABLED, 21 | None, 22 | None, 23 | None, 24 | True, 25 | None, 26 | None, 27 | ], 28 | } 29 | 30 | 31 | async def async_setup_entry(hass, entry, async_add_devices): 32 | """Setup sensor platform.""" 33 | # coordinator = hass.data[DOMAIN][entry.entry_id] 34 | # async_add_devices([WaterkotteHeatpumpBinarySwitch(coordinator, entry)]) 35 | # hass_data = hass.data[DOMAIN][entry.entry_id] 36 | _LOGGER.debug("Sensor async_setup_entry") 37 | coordinator = hass.data[DOMAIN][entry.entry_id] 38 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_condensation")]) 39 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_evaporation")]) 40 | async_add_devices( 41 | [ 42 | WaterkotteHeatpumpBinarySwitch(entry, coordinator, sensor_type) 43 | for sensor_type in SENSOR_TYPES 44 | ] 45 | ) 46 | 47 | 48 | class WaterkotteHeatpumpBinarySwitch(WaterkotteHeatpumpEntity, SwitchEntity): 49 | """waterkotte_heatpump switch class.""" 50 | 51 | def __init__( 52 | self, entry, hass_data, sensor_type 53 | ): # pylint: disable=unused-argument 54 | """Initialize the sensor.""" 55 | self._coordinator = hass_data 56 | 57 | self._type = sensor_type 58 | # self._name = f"{SENSOR_TYPES[self._type][0]} {DOMAIN}" 59 | self._name = f"{SENSOR_TYPES[self._type][0]}" 60 | # self._unique_id = f"{SENSOR_TYPES[self._type][0]}_{DOMAIN}" 61 | # self._unique_id = f"{self._type}_{DOMAIN}" 62 | self._unique_id = self._type 63 | self._entry_data = entry.data 64 | self._device_id = entry.entry_id 65 | hass_data.alltags.update({self._unique_id: SENSOR_TYPES[self._type][1]}) 66 | super().__init__(hass_data, entry) 67 | # self._attr_capability_attributes[ATTR_FRIENDLY_NAME] = self._name 68 | 69 | def __del__(self): 70 | try: 71 | del self._coordinator[self._unique_id] 72 | except Exception: # pylint: disable=broad-except 73 | pass 74 | 75 | async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument 76 | """Turn on the switch.""" 77 | # await self.coordinator.api.async_set_title("bar") 78 | # await self.coordinator.async_request_refresh() 79 | try: 80 | # print(option) 81 | # await self._coordinator.api.async_write_value(SENSOR_TYPES[self._type][1], option) 82 | await self._coordinator.async_write_tag(SENSOR_TYPES[self._type][1], True) 83 | sensor = SENSOR_TYPES[self._type] 84 | return self._coordinator.data[sensor[1]]["value"] 85 | except ValueError: 86 | return "unavailable" 87 | 88 | async def async_turn_off(self, **kwargs): # pylint: disable=unused-argument 89 | """Turn off the switch.""" 90 | # await self.coordinator.api.async_set_title("foo") 91 | # await self.coordinator.async_request_refresh() 92 | try: 93 | # print(option) 94 | # await self._coordinator.api.async_write_value(SENSOR_TYPES[self._type][1], option) 95 | await self._coordinator.async_write_tag(SENSOR_TYPES[self._type][1], False) 96 | sensor = SENSOR_TYPES[self._type] 97 | return self._coordinator.data[sensor[1]]["value"] 98 | except ValueError: 99 | return "unavailable" 100 | 101 | @property 102 | def is_on(self) -> bool | None: 103 | """Return true if the binary_sensor is on.""" 104 | # return self.coordinator.data.get("title", "") == "foo" 105 | try: 106 | sensor = SENSOR_TYPES[self._type] 107 | value = self._coordinator.data[sensor[1]]["value"] 108 | if value is None or value == "": 109 | value = None 110 | except KeyError: 111 | value = None 112 | print(value) 113 | except TypeError: 114 | return None 115 | return value 116 | 117 | @property 118 | def tag(self): 119 | """Return a unique ID to use for this entity.""" 120 | return SENSOR_TYPES[self._type][1] 121 | 122 | @property 123 | def name(self): 124 | """Return the name of the sensor.""" 125 | return self._name 126 | 127 | @property 128 | def icon(self): 129 | """Return the icon of the sensor.""" 130 | if SENSOR_TYPES[self._type][4] is None: 131 | sensor = SENSOR_TYPES[self._type] 132 | try: 133 | if ( 134 | self._type == "holiday_enabled" 135 | and sensor[1] in self._coordinator.data 136 | ): 137 | if self._coordinator.data[sensor[1]]["value"] is True: 138 | return "mdi:calendar-check" 139 | else: 140 | return "mdi:calendar-blank" 141 | else: 142 | return None 143 | except KeyError: 144 | print( 145 | f"KeyError in switch.icon: should have value? data:{self._coordinator.data[sensor[1]]}" 146 | ) 147 | except TypeError: 148 | return None 149 | return SENSOR_TYPES[self._type][4] 150 | # return ICON 151 | 152 | # @property 153 | # def is_on(self): 154 | # """Return true if the switch is on.""" 155 | # return self.coordinator.data.get("title", "") == "foo" 156 | 157 | @property 158 | def entity_registry_enabled_default(self): 159 | """Return the entity_registry_enabled_default of the sensor.""" 160 | return SENSOR_TYPES[self._type][5] 161 | 162 | @property 163 | def unique_id(self): 164 | """Return the unique of the sensor.""" 165 | return self._unique_id 166 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Binary sensor platform for Waterkotte Heatpump.""" 2 | import logging 3 | from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass 4 | from homeassistant.helpers.typing import ConfigType, HomeAssistantType 5 | # from homeassistant.const import ATTR_FRIENDLY_NAME 6 | 7 | # from .const import DOMAIN 8 | from pywaterkotte.ecotouch import EcotouchTag 9 | from .entity import WaterkotteHeatpumpEntity 10 | 11 | # from pywaterkotte.ecotouch import EcotouchTag 12 | from .const import DOMAIN # , NAME, CONF_FW, CONF_BIOS, CONF_IP 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | # Sensor types are defined as: 18 | # variable -> [0]title, [1] EcoTouchTag, [2]device_class, [3]units, [4]icon, [5]enabled_by_default, [6]options, [7]entity_category #pylint: disable=line-too-long 19 | SENSOR_TYPES = { 20 | "state_sourcepump": [ 21 | "Sourcepump", 22 | EcotouchTag.STATE_SOURCEPUMP, 23 | BinarySensorDeviceClass.RUNNING, 24 | None, 25 | None, 26 | True, 27 | None, 28 | None, 29 | ], 30 | "state_heatingpump": [ 31 | "Heatingpump", 32 | EcotouchTag.STATE_HEATINGPUMP, 33 | BinarySensorDeviceClass.RUNNING, 34 | None, 35 | None, 36 | True, 37 | None, 38 | None, 39 | ], 40 | "state_evd": [ 41 | "EVD", 42 | EcotouchTag.STATE_EVD, 43 | BinarySensorDeviceClass.RUNNING, 44 | None, 45 | None, 46 | False, 47 | None, 48 | None, 49 | ], 50 | "state_compressor": [ 51 | "Compressor", 52 | EcotouchTag.STATE_COMPRESSOR, 53 | BinarySensorDeviceClass.RUNNING, 54 | None, 55 | None, 56 | False, 57 | None, 58 | None, 59 | ], 60 | "state_compressor2": [ 61 | "Compressor2", 62 | EcotouchTag.STATE_COMPRESSOR2, 63 | BinarySensorDeviceClass.RUNNING, 64 | None, 65 | None, 66 | False, 67 | None, 68 | None, 69 | ], 70 | "state_external_heater": [ 71 | "External Heater", 72 | EcotouchTag.STATE_EXTERNAL_HEATER, 73 | BinarySensorDeviceClass.RUNNING, 74 | None, 75 | None, 76 | False, 77 | None, 78 | None, 79 | ], 80 | "state_alarm": [ 81 | "Alarm", 82 | EcotouchTag.STATE_ALARM, 83 | BinarySensorDeviceClass.RUNNING, 84 | None, 85 | None, 86 | False, 87 | None, 88 | None, 89 | ], 90 | "state_cooling": [ 91 | "Cooling", 92 | EcotouchTag.STATE_COOLING, 93 | BinarySensorDeviceClass.RUNNING, 94 | None, 95 | None, 96 | False, 97 | None, 98 | None, 99 | ], 100 | "state_water": [ 101 | "Water", 102 | EcotouchTag.STATE_WATER, 103 | BinarySensorDeviceClass.RUNNING, 104 | None, 105 | None, 106 | False, 107 | None, 108 | None, 109 | ], 110 | "state_pool": [ 111 | "Pool", 112 | EcotouchTag.STATE_POOL, 113 | BinarySensorDeviceClass.RUNNING, 114 | None, 115 | None, 116 | True, 117 | None, 118 | None, 119 | ], 120 | "state_solar": [ 121 | "Solar", 122 | EcotouchTag.STATE_SOLAR, 123 | BinarySensorDeviceClass.RUNNING, 124 | None, 125 | "mdi:weather-partly-cloudy", 126 | False, 127 | None, 128 | None, 129 | ], 130 | "state_cooling4way": [ 131 | "Cooling4way", 132 | EcotouchTag.STATE_COOLING4WAY, 133 | BinarySensorDeviceClass.RUNNING, 134 | None, 135 | None, 136 | False, 137 | None, 138 | None, 139 | ], 140 | "heatingmode_mixing1": [ 141 | "Heating mode Mixing 1", 142 | EcotouchTag.HEATINGMODE_MIXING1, 143 | BinarySensorDeviceClass.RUNNING, 144 | None, 145 | None, 146 | False, 147 | None, 148 | None, 149 | ], 150 | "heatingmode_mixing2": [ 151 | "Heating mode Mixing 2", 152 | EcotouchTag.HEATINGMODE_MIXING2, 153 | BinarySensorDeviceClass.RUNNING, 154 | None, 155 | None, 156 | False, 157 | None, 158 | None, 159 | ], 160 | "heatingmode_mixing3": [ 161 | "Heating mode Mixing 3", 162 | EcotouchTag.HEATINGMODE_MIXING3, 163 | BinarySensorDeviceClass.RUNNING, 164 | None, 165 | None, 166 | False, 167 | None, 168 | None, 169 | ], 170 | # "holiday_enabled": [ 171 | # "Holiday Mode", 172 | # EcotouchTag.HOLIDAY_ENABLED, 173 | # BinarySensorDeviceClass.RUNNING, 174 | # None, 175 | # None, 176 | # True, 177 | # None, 178 | # None, 179 | # ], 180 | 181 | 182 | } 183 | 184 | # async def async_setup_entry(hass, entry, async_add_devices): 185 | # """Setup binary_sensor platform.""" 186 | # coordinator = hass.data[DOMAIN][entry.entry_id] 187 | # async_add_devices([WaterkotteHeatpumpBinarySensor(coordinator, entry)]) 188 | 189 | 190 | async def async_setup_entry(hass: HomeAssistantType, entry: ConfigType, async_add_devices) -> None: 191 | """Set up the Waterkotte sensor platform.""" 192 | # hass_data = hass.data[DOMAIN][entry.entry_id] 193 | _LOGGER.debug("Sensor async_setup_entry") 194 | coordinator = hass.data[DOMAIN][entry.entry_id] 195 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_condensation")]) 196 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_evaporation")]) 197 | async_add_devices([WaterkotteHeatpumpBinarySensor(entry, coordinator, sensor_type) 198 | for sensor_type in SENSOR_TYPES]) 199 | 200 | 201 | class WaterkotteHeatpumpBinarySensor(WaterkotteHeatpumpEntity, BinarySensorEntity): 202 | """waterkotte_heatpump binary_sensor class.""" 203 | # _attr_has_entity_name = True 204 | 205 | def __init__(self, entry, hass_data, sensor_type): # pylint: disable=unused-argument 206 | """Initialize the sensor.""" 207 | self._coordinator = hass_data 208 | 209 | self._type = sensor_type 210 | # self._name = f"{SENSOR_TYPES[self._type][0]} {DOMAIN}" 211 | self._name = f"{SENSOR_TYPES[self._type][0]}" 212 | # self._unique_id = f"{SENSOR_TYPES[self._type][0]}_{DOMAIN}" 213 | # self._unique_id = f"{self._type}_{DOMAIN}" 214 | self._unique_id = self._type 215 | self._entry_data = entry.data 216 | self._device_id = entry.entry_id 217 | hass_data.alltags.update({self._unique_id: SENSOR_TYPES[self._type][1]}) 218 | super().__init__(hass_data, entry) 219 | # self._attr_capability_attributes[ATTR_FRIENDLY_NAME] = self._name 220 | 221 | @property 222 | def tag(self): 223 | """Return a tag to use for this entity.""" 224 | return SENSOR_TYPES[self._type][1] 225 | 226 | @ property 227 | def name(self): 228 | """Return the name of the sensor.""" 229 | return self._name 230 | 231 | @property 232 | def is_on(self) -> bool | None: 233 | """Return true if the binary_sensor is on.""" 234 | # return self.coordinator.data.get("title", "") == "foo" 235 | try: 236 | sensor = SENSOR_TYPES[self._type] 237 | value = self._coordinator.data[sensor[1]]["value"] 238 | if value is None or value == "": 239 | value = None 240 | except KeyError: 241 | value = None 242 | #print(value) 243 | except TypeError: 244 | return None 245 | return value 246 | 247 | @ property 248 | def icon(self): 249 | """Return the icon of the sensor.""" 250 | if SENSOR_TYPES[self._type][4] is None: 251 | sensor = SENSOR_TYPES[self._type] 252 | try: 253 | if self._type == "holiday_enabled" and 'value' in self._coordinator.data[sensor[1]]: 254 | if self._coordinator.data[sensor[1]]["value"] is True: 255 | return "mdi:calendar-check" 256 | else: 257 | return "mdi:calendar-blank" 258 | else: 259 | return None 260 | except KeyError: 261 | print(f"KeyError in Binary_sensor.icon: should have value? data:{self._coordinator.data[sensor[1]]}") # pylint: disable=line-too-long 262 | return SENSOR_TYPES[self._type][4] 263 | # return ICON 264 | 265 | @ property 266 | def device_class(self): 267 | """Return the device class of the sensor.""" 268 | return SENSOR_TYPES[self._type][2] 269 | 270 | @ property 271 | def entity_registry_enabled_default(self): 272 | """Return the entity_registry_enabled_default of the sensor.""" 273 | return SENSOR_TYPES[self._type][5] 274 | 275 | @ property 276 | def entity_category(self): 277 | """Return the unit of measurement.""" 278 | try: 279 | return SENSOR_TYPES[self._type][7] 280 | except IndexError: 281 | return None 282 | 283 | @ property 284 | def unique_id(self): 285 | """Return the unique of the sensor.""" 286 | return self._unique_id 287 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/config_flow.py: -------------------------------------------------------------------------------- 1 | """Adds config flow for Waterkotte Heatpump.""" 2 | # from os import system 3 | from socket import gethostbyname 4 | import voluptuous as vol 5 | 6 | from homeassistant import config_entries 7 | from homeassistant.core import callback 8 | from homeassistant.helpers.aiohttp_client import async_create_clientsession 9 | from homeassistant.helpers.selector import selector 10 | 11 | # import api 12 | from .const import ( 13 | CONF_POLLING_INTERVAL, 14 | CONF_BIOS, 15 | CONF_FW, 16 | CONF_SERIAL, 17 | CONF_ID, 18 | CONF_SERIES, 19 | CONF_SYSTEMTYPE, 20 | CONF_HOST, 21 | CONF_IP, 22 | CONF_PASSWORD, 23 | CONF_USERNAME, 24 | ) 25 | from .const import DOMAIN, SELECT, SENSOR, BINARY_SENSOR, TITLE 26 | 27 | from .api import WaterkotteHeatpumpApiClient 28 | from pywaterkotte.ecotouch import EcotouchTag 29 | from pywaterkotte.detect import waterkotte_detect, EASYCON, ECOTOUCH 30 | 31 | # import homeassistant.helpers.config_validation as cv 32 | 33 | # from custom_components.pywaterkotte import pywaterkotte 34 | # from .pywaterkotte.ecotouch import Ecotouch, EcotouchTag 35 | 36 | 37 | class WaterkotteHeatpumpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 38 | """Config flow for waterkotte_heatpump.""" 39 | 40 | VERSION = 1 41 | CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL 42 | 43 | def __init__(self): 44 | """Initialize.""" 45 | self._errors = {} 46 | self._ip = "" 47 | self._bios = "" 48 | self._firmware = "" 49 | self._ID = "" # pylint: disable=invalid-name 50 | self._series = "" 51 | self._serial = "" 52 | self._system_type = "" 53 | 54 | async def async_step_user(self, user_input=None): 55 | """Handle a flow initialized by the user.""" 56 | self._errors = {} 57 | 58 | # Uncomment the next 2 lines if only a single instance of the integration is allowed: 59 | # if self._async_current_entries(): 60 | # return self.async_abort(reason="single_instance_allowed") 61 | 62 | if user_input is not None: 63 | valid = await self._test_credentials( 64 | user_input[CONF_USERNAME], 65 | user_input[CONF_PASSWORD], 66 | user_input[CONF_HOST], 67 | user_input[CONF_SYSTEMTYPE], 68 | ) 69 | if valid: 70 | user_input[CONF_IP] = self._ip 71 | user_input[CONF_BIOS] = self._bios 72 | user_input[CONF_FW] = self._firmware 73 | user_input[CONF_SERIES] = self._series 74 | user_input[CONF_SERIAL] = self._serial 75 | user_input[CONF_ID] = self._ID 76 | user_input[CONF_SYSTEMTYPE] = self._system_type 77 | return self.async_create_entry(title=TITLE, data=user_input) 78 | else: 79 | self._errors["base"] = "auth" 80 | 81 | return await self._show_config_form(user_input) 82 | 83 | return await self._show_config_form(user_input) 84 | 85 | @staticmethod 86 | @callback 87 | def async_get_options_flow(config_entry): 88 | return WaterkotteHeatpumpOptionsFlowHandler(config_entry) 89 | 90 | async def _show_config_form(self, user_input): # pylint: disable=unused-argument 91 | """Show the configuration form to edit location data.""" 92 | return self.async_show_form( 93 | step_id="user", 94 | data_schema=vol.Schema( 95 | { 96 | vol.Required(CONF_HOST, default=""): str, 97 | vol.Required(CONF_USERNAME, default="waterkotte"): str, 98 | vol.Required(CONF_PASSWORD, default="waterkotte"): str, 99 | vol.Required(CONF_SYSTEMTYPE, default=ECOTOUCH): selector( 100 | { 101 | "select": { 102 | "options": [ 103 | {"label": "EcoTouch Mode", "value": ECOTOUCH}, 104 | {"label": "EasyCon Mode", "value": EASYCON}, 105 | ], 106 | "mode": "dropdown", 107 | } 108 | } 109 | ), 110 | } 111 | ), 112 | errors=self._errors, 113 | ) 114 | 115 | async def _test_credentials(self, username, password, host, systemType): 116 | """Return true if credentials is valid.""" 117 | try: 118 | # # session = async_create_clientsession(self.hass) 119 | # client = Ecotouch(host) 120 | # await client.login(username, password) 121 | # ret = await client.read_value(EcotouchTag.DATE_DAY) 122 | # # print(ret) 123 | # return ret["status"] == "E_OK" 124 | # # await client.async_get_data() 125 | hasPort = host.find(":") 126 | if hasPort == -1: 127 | self._ip = gethostbyname(host) 128 | else: 129 | self._ip = gethostbyname(host[:hasPort]) 130 | session = async_create_clientsession(self.hass) 131 | # detect system 132 | # system_type = await waterkotte_detect(host, username, password) 133 | # if system_type == ECOTOUCH: 134 | # print("Detected EcoTouch System") 135 | # elif system_type == EASYCON: 136 | # print("Detected EasyCon System") 137 | # else: 138 | # print("Could not detect System Type!") 139 | 140 | client = WaterkotteHeatpumpApiClient( 141 | host, username, password, session, None, systemType=systemType 142 | ) 143 | await client.login() 144 | # await client.async_read_value(EcotouchTag.DATE_DAY) 145 | inittag = [ 146 | EcotouchTag.VERSION_BIOS, 147 | EcotouchTag.VERSION_CONTROLLER, 148 | # EcotouchTag.VERSION_CONTROLLER_BUILD, 149 | EcotouchTag.INFO_ID, 150 | EcotouchTag.INFO_SERIAL, 151 | EcotouchTag.INFO_SERIES, 152 | ] 153 | ret = await client.async_read_values(inittag) 154 | self._bios = ret[EcotouchTag.VERSION_BIOS]["value"] 155 | self._firmware = ret[EcotouchTag.VERSION_CONTROLLER]["value"] 156 | self._ID = str(ret[EcotouchTag.INFO_ID]["value"]) 157 | self._series = str(ret[EcotouchTag.INFO_SERIES]["value"]) 158 | self._serial = str(ret[EcotouchTag.INFO_SERIAL]["value"]) 159 | self._system_type = systemType 160 | # print(ret) 161 | return True 162 | 163 | except Exception: # pylint: disable=broad-except 164 | pass 165 | return False 166 | 167 | 168 | class WaterkotteHeatpumpOptionsFlowHandler(config_entries.OptionsFlow): 169 | """Config flow options handler for waterkotte_heatpump.""" 170 | 171 | def __init__(self, config_entry): 172 | """Initialize HACS options flow.""" 173 | self.config_entry = config_entry 174 | if len(dict(config_entry.options)) == 0: 175 | self.options = dict(config_entry.data) 176 | else: 177 | self.options = dict(config_entry.options) 178 | 179 | async def async_step_init(self, user_input=None): # pylint: disable=unused-argument 180 | """Manage the options.""" 181 | return await self.async_step_user() 182 | 183 | async def async_step_user(self, user_input=None): 184 | """Handle a flow initialized by the user.""" 185 | if user_input is not None: 186 | self.options.update(user_input) 187 | return await self._update_options() 188 | 189 | dataSchema = vol.Schema( 190 | { 191 | # vol.Required( 192 | # BINARY_SENSOR, default=self.options.get(BINARY_SENSOR, True) 193 | # ): bool, 194 | # vol.Required(SENSOR, default=self.options.get(SENSOR, True)): bool, 195 | # vol.Required(SELECT, default=self.options.get(SELECT, True)): bool, 196 | vol.Required( 197 | CONF_POLLING_INTERVAL, 198 | default=self.options.get(CONF_POLLING_INTERVAL, 30), 199 | ): int, # pylint: disable=line-too-long 200 | vol.Required( 201 | CONF_USERNAME, default=self.options.get(CONF_USERNAME) 202 | ): str, 203 | vol.Required( 204 | CONF_PASSWORD, default=self.options.get(CONF_USERNAME) 205 | ): str, 206 | vol.Required( 207 | CONF_SYSTEMTYPE, default=self.options.get(CONF_SYSTEMTYPE) 208 | ): selector( 209 | { 210 | "select": { 211 | "options": [ 212 | {"label": "EcoTouch Mode", "value": ECOTOUCH}, 213 | {"label": "EasyCon Mode", "value": EASYCON}, 214 | ], 215 | "mode": "dropdown", 216 | } 217 | } 218 | ), 219 | } 220 | ) 221 | 222 | return self.async_show_form( 223 | step_id="user", 224 | data_schema=dataSchema, 225 | ) 226 | 227 | async def _update_options(self): 228 | """Update config entry options.""" 229 | return self.async_create_entry(title=TITLE, data=self.options) 230 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/select.py: -------------------------------------------------------------------------------- 1 | """Sensor platform for Waterkotte Heatpump.""" 2 | import logging 3 | # from homeassistant.helpers.entity import Entity, EntityCategory # , DeviceInfo 4 | from homeassistant.components.select import SelectEntity 5 | from homeassistant.helpers.typing import ConfigType, HomeAssistantType 6 | 7 | # from .const import DEFAULT_NAME 8 | 9 | 10 | # from .const import ICON 11 | # from .const import SENSOR 12 | 13 | # from .const import UnitOfTemperature 14 | 15 | 16 | # from homeassistant.const import ( 17 | # ATTR_ATTRIBUTION, 18 | # DEGREE, 19 | # DEVICE_CLASS_HUMIDITY, 20 | # DEVICE_CLASS_PRESSURE, 21 | # DEVICE_CLASS_TEMPERATURE, 22 | # LENGTH_KILOMETERS, 23 | # PRESSURE_HPA, 24 | # PRESSURE_BAR, 25 | # SPEED_KILOMETERS_PER_HOUR, 26 | # TEMP_CELSIUS, 27 | # TIME_SECONDS, 28 | # ) 29 | from pywaterkotte.ecotouch import EcotouchTag 30 | from .entity import WaterkotteHeatpumpEntity 31 | 32 | from .const import ENUM_OFFAUTOMANUAL, DEVICE_CLASS_ENUM, DOMAIN 33 | 34 | _LOGGER = logging.getLogger(__name__) 35 | 36 | 37 | # Sensor types are defined as: 38 | # variable -> [0]title, [1] EcoTouchTag, [2]device_class, [3]units, [4]icon, [5]enabled_by_default, [6]options, [7]entity_category #pylint: disable=line-too-long 39 | SENSOR_TYPES = { 40 | "enable_cooling": [ 41 | "Enable Cooling", 42 | EcotouchTag.ENABLE_COOLING, 43 | DEVICE_CLASS_ENUM, 44 | None, 45 | "mdi:snowflake-thermometer", 46 | True, 47 | ENUM_OFFAUTOMANUAL, 48 | None, 49 | ], 50 | "enable_heating": [ 51 | "Enable Heating", 52 | EcotouchTag.ENABLE_HEATING, 53 | DEVICE_CLASS_ENUM, 54 | None, 55 | "mdi:weather-partly-cloudy", 56 | True, 57 | ENUM_OFFAUTOMANUAL, 58 | None, 59 | ], 60 | "enable_pv": [ 61 | "Enable PV", 62 | EcotouchTag.ENABLE_PV, 63 | DEVICE_CLASS_ENUM, 64 | None, 65 | "mdi:solar-power", 66 | False, 67 | ENUM_OFFAUTOMANUAL, 68 | None, 69 | ], 70 | "enable_warmwater": [ 71 | "Enable Warmwater", 72 | EcotouchTag.ENABLE_WARMWATER, 73 | DEVICE_CLASS_ENUM, 74 | None, 75 | "mdi:water-thermometer", 76 | True, 77 | ENUM_OFFAUTOMANUAL, 78 | None, 79 | ], 80 | "enable_mixing1": [ 81 | "Enable Mixing 1", 82 | EcotouchTag.ENABLE_MIXING1, 83 | DEVICE_CLASS_ENUM, 84 | None, 85 | "mdi:water-thermometer", 86 | False, 87 | ENUM_OFFAUTOMANUAL, 88 | None, 89 | ], 90 | "enable_mixing2": [ 91 | "Enable Mixing 2", 92 | EcotouchTag.ENABLE_MIXING2, 93 | DEVICE_CLASS_ENUM, 94 | None, 95 | "mdi:water-thermometer", 96 | False, 97 | ENUM_OFFAUTOMANUAL, 98 | None, 99 | ], 100 | "enable_mixing3": [ 101 | "Enable Mixing 3", 102 | EcotouchTag.ENABLE_MIXING3, 103 | DEVICE_CLASS_ENUM, 104 | None, 105 | "mdi:water-thermometer", 106 | False, 107 | ENUM_OFFAUTOMANUAL, 108 | None, 109 | ], 110 | } 111 | """ 112 | 113 | TEMPERATURE_HEATING_SET = TagData(["A31"], "°C") 114 | TEMPERATURE_HEATING_SET2 = TagData(["A32"], "°C") 115 | TEMPERATURE_COOLING_SET = TagData(["A34"], "°C") 116 | TEMPERATURE_COOLING_SET2 = TagData(["A35"], "°C") 117 | TEMPERATURE_WATER_SETPOINT = TagData(["A37"], "°C", writeable=True) 118 | TEMPERATURE_WATER_SETPOINT2 = TagData(["A38"], "°C", writeable=True) 119 | TEMPERATURE_POOL_SETPOINT = TagData(["A40"], "°C", writeable=True) 120 | TEMPERATURE_POOL_SETPOINT2 = TagData(["A41"], "°C", writeable=True) 121 | COMPRESSOR_POWER = TagData(["A50"], "?°C") 122 | 123 | HYSTERESIS_HEATING = TagData(["A61"], "?") 124 | 125 | NVI_NORM_AUSSEN = TagData(["A91"], "?") 126 | NVI_HEIZKREIS_NORM = TagData(["A92"], "?") 127 | NVI_T_HEIZGRENZE = TagData(["A93"], "?°C") 128 | NVI_T_HEIZGRENZE_SOLL = TagData(["A94"], "?°C") 129 | MAX_VL_TEMP = TagData(["A95"], "°C") 130 | TEMP_SET_0_DEG = TagData(["A97"], "°C") 131 | COOL_ENABLE_TEMP = TagData(["A108"], "°C") 132 | NVI_SOLL_KUEHLEN = TagData(["A109"], "°C") 133 | TEMPCHANGE_HEATING_PV = TagData(["A682"], "°C") 134 | TEMPCHANGE_COOLING_PV = TagData(["A683"], "°C") 135 | TEMPCHANGE_WARMWATER_PV = TagData(["A684"], "°C") 136 | TEMPCHANGE_POOL_PV = TagData(["A685"], "°C") 137 | 138 | DATE_DAY = TagData(["I5"]) 139 | DATE_MONTH = TagData(["I6"]) 140 | DATE_YEAR = TagData(["I7"]) 141 | TIME_HOUR = TagData(["I8"]) 142 | TIME_MINUTE = TagData(["I9"]) 143 | OPERATING_HOURS_COMPRESSOR_1 = TagData(["I10"]) 144 | OPERATING_HOURS_COMPRESSOR_2 = TagData(["I14"]) 145 | OPERATING_HOURS_CIRCULATION_PUMP = TagData(["I18"]) 146 | OPERATING_HOURS_SOURCE_PUMP = TagData(["I20"]) 147 | OPERATING_HOURS_SOLAR = TagData(["I22"]) 148 | 149 | ALARM = TagData(["I52"]) 150 | INTERRUPTIONS = TagData(["I53"]) 151 | ADAPT_HEATING = TagData(["I263"], writeable=True) 152 | MANUAL_HEATINGPUMP = TagData(["I1270"]) 153 | MANUAL_SOURCEPUMP = TagData(["I1281"]) 154 | MANUAL_SOLARPUMP1 = TagData(["I1287"]) 155 | MANUAL_SOLARPUMP2 = TagData(["I1289"]) 156 | MANUAL_TANKPUMP = TagData(["I1291"]) 157 | MANUAL_VALVE = TagData(["I1293"]) 158 | MANUAL_POOLVALVE = TagData(["I1295"]) 159 | MANUAL_COOLVALVE = TagData(["I1297"]) 160 | MANUAL_4WAYVALVE = TagData(["I1299"]) 161 | MANUAL_MULTIEXT = TagData(["I1319"]) """ 162 | 163 | 164 | async def async_setup_entry(hass: HomeAssistantType, entry: ConfigType, async_add_devices) -> None: 165 | """Set up the Waterkotte sensor platform.""" 166 | # hass_data = hass.data[DOMAIN][entry.entry_id] 167 | _LOGGER.debug("Sensor async_setup_entry") 168 | coordinator = hass.data[DOMAIN][entry.entry_id] 169 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_condensation")]) 170 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_evaporation")]) 171 | async_add_devices([WaterkotteHeatpumpSelect(entry, coordinator, sensor_type) 172 | for sensor_type in SENSOR_TYPES]) 173 | 174 | 175 | class WaterkotteHeatpumpSelect(SelectEntity, WaterkotteHeatpumpEntity): 176 | """waterkotte_heatpump Sensor class.""" 177 | 178 | def __init__(self, entry, hass_data, sensor_type): # pylint: disable=unused-argument 179 | """Initialize the sensor.""" 180 | self._coordinator = hass_data 181 | 182 | self._type = sensor_type 183 | # self._name = f"{SENSOR_TYPES[self._type][0]} {DOMAIN}" 184 | self._name = f"{SENSOR_TYPES[self._type][0]}" 185 | # self._unique_id = f"{SENSOR_TYPES[self._type][0]}_{DOMAIN}" 186 | self._unique_id = self._type 187 | self._entry_data = entry.data 188 | self._device_id = entry.entry_id 189 | hass_data.alltags.update({self._unique_id: SENSOR_TYPES[self._type][1]}) 190 | super().__init__(hass_data, entry) 191 | 192 | @ property 193 | def name(self): 194 | """Return the name of the sensor.""" 195 | return self._name 196 | 197 | @property 198 | def current_option(self) -> str | None: 199 | try: 200 | sensor = SENSOR_TYPES[self._type] 201 | value = self._coordinator.data[sensor[1]]["value"] 202 | if value is None or value == "": 203 | value = 'unknown' 204 | except KeyError: 205 | value = "unknown" 206 | except TypeError: 207 | return None 208 | return value 209 | # return "auto" 210 | 211 | @property 212 | def options(self) -> list[str]: 213 | # return ["off", "auto", "manual"] 214 | return SENSOR_TYPES[self._type][6] 215 | 216 | async def async_select_option(self, option: str) -> None: # pylint: disable=unused-argument 217 | """Turn on the switch.""" 218 | try: 219 | # print(option) 220 | # await self._coordinator.api.async_write_value(SENSOR_TYPES[self._type][1], option) 221 | await self._coordinator.async_write_tag(SENSOR_TYPES[self._type][1], option) 222 | except ValueError: 223 | return "unavailable" 224 | 225 | @property 226 | def tag(self): 227 | """Return a unique ID to use for this entity.""" 228 | return SENSOR_TYPES[self._type][1] 229 | 230 | @ property 231 | def icon(self): 232 | """Return the icon of the sensor.""" 233 | return SENSOR_TYPES[self._type][4] 234 | # return ICON 235 | 236 | @ property 237 | def device_class(self): 238 | """Return the device class of the sensor.""" 239 | return SENSOR_TYPES[self._type][2] 240 | 241 | @ property 242 | def entity_registry_enabled_default(self): 243 | """Return the entity_registry_enabled_default of the sensor.""" 244 | return SENSOR_TYPES[self._type][5] 245 | 246 | @ property 247 | def entity_category(self): 248 | """Return the unit of measurement.""" 249 | try: 250 | return SENSOR_TYPES[self._type][7] 251 | except IndexError: 252 | return None 253 | 254 | @ property 255 | def unique_id(self): 256 | """Return the unique of the sensor.""" 257 | return self._unique_id 258 | 259 | @ property 260 | def unit_of_measurement(self): 261 | """Return the unit of measurement.""" 262 | return SENSOR_TYPES[self._type][3] 263 | 264 | async def async_update(self): 265 | """Schedule a custom update via the common entity update service.""" 266 | await self._coordinator.async_request_refresh() 267 | 268 | @ property 269 | def should_poll(self) -> bool: 270 | """Entities do not individually poll.""" 271 | return False 272 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom integration to integrate Waterkotte Heatpump with Home Assistant. 3 | 4 | For more details about this integration, please refer to 5 | https://github.com/pattisonmichael/waterkotte-heatpump 6 | """ 7 | import asyncio 8 | import logging 9 | import re 10 | import json 11 | from typing import Sequence 12 | from datetime import timedelta 13 | from homeassistant.config_entries import ConfigEntry 14 | from homeassistant.core import Config 15 | from homeassistant.core import HomeAssistant 16 | from homeassistant.exceptions import ConfigEntryNotReady 17 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 18 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 19 | from homeassistant.helpers.update_coordinator import UpdateFailed 20 | from homeassistant.helpers.entity_registry import async_entries_for_device 21 | from homeassistant.helpers.entity_registry import async_get as getEntityRegistry 22 | from homeassistant.helpers.entity import DeviceInfo 23 | from homeassistant.helpers import device_registry as dr 24 | from homeassistant.core import SupportsResponse 25 | from pywaterkotte.ecotouch import EcotouchTag 26 | from .api import WaterkotteHeatpumpApiClient 27 | from .const import CONF_IP, CONF_BIOS, CONF_FW, CONF_SERIAL, CONF_SERIES, CONF_ID 28 | from .const import ( 29 | CONF_HOST, 30 | CONF_PASSWORD, 31 | CONF_USERNAME, 32 | CONF_POLLING_INTERVAL, 33 | CONF_SYSTEMTYPE, 34 | ) 35 | from .const import DOMAIN, NAME, TITLE 36 | from .const import PLATFORMS 37 | from .const import STARTUP_MESSAGE 38 | 39 | from . import service as waterkotteservice 40 | 41 | # from .const import SENSORS 42 | 43 | SCAN_INTERVAL = timedelta(seconds=60) 44 | COORDINATOR = None 45 | _LOGGER: logging.Logger = logging.getLogger(__package__) 46 | LANG = None 47 | tags = [] 48 | 49 | 50 | async def async_setup( 51 | hass: HomeAssistant, config: Config 52 | ): # pylint: disable=unused-argument 53 | """Set up this integration using YAML is not supported.""" 54 | return True 55 | 56 | 57 | def load_translation(hass): 58 | """Load correct language file or defailt to english""" 59 | global LANG # pylint: disable=global-statement 60 | basepath = __file__[:-11] 61 | file = f"{basepath}translations/heatpump.{hass.config.country.lower()}.json" 62 | try: 63 | with open(file) as f: # pylint: disable=unspecified-encoding,invalid-name 64 | LANG = json.load(f) 65 | except: # pylint: disable=unspecified-encoding,bare-except,invalid-name 66 | with open(f"{basepath}translations/heatpump.en.json") as f: 67 | LANG = json.load(f) 68 | 69 | 70 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 71 | """Set up this integration using UI.""" 72 | global SCAN_INTERVAL # pylint: disable=global-statement 73 | global COORDINATOR # pylint: disable=global-statement 74 | if hass.data.get(DOMAIN) is None: 75 | hass.data.setdefault(DOMAIN, {}) 76 | _LOGGER.info(STARTUP_MESSAGE) 77 | 78 | # Setup Device 79 | fw = entry.options.get( 80 | CONF_IP, entry.data.get(CONF_IP) 81 | ) # pylint: disable=invalid-name 82 | bios = entry.options.get(CONF_BIOS, entry.data.get(CONF_BIOS)) 83 | 84 | device_registry = dr.async_get(hass) 85 | 86 | device_registry.async_get_or_create( # pylint: disable=invalid-name 87 | config_entry_id=entry.entry_id, 88 | identifiers={ 89 | ("DOMAIN", DOMAIN), 90 | ("IP", entry.options.get(CONF_IP, entry.data.get(CONF_IP))), 91 | }, 92 | manufacturer=NAME, 93 | suggested_area="Basement", 94 | name=NAME, 95 | model=entry.options.get(CONF_SERIES, entry.data.get(CONF_SERIES)), 96 | sw_version=f"{fw} BIOS: {bios}", 97 | hw_version=entry.options.get(CONF_ID, entry.data.get(CONF_ID)), 98 | ) 99 | 100 | # device = DeviceInfo( 101 | # id=deviceEntry.id, 102 | # identifiers=deviceEntry.identifiers, 103 | # name=deviceEntry.name, 104 | # manufacturer=deviceEntry.manufacturer, 105 | # model=deviceEntry.model, 106 | # sw_version=deviceEntry.sw_version, 107 | # suggested_area=deviceEntry.suggested_area, 108 | # hw_version=deviceEntry.hw_version, 109 | # ) 110 | 111 | ### 112 | load_translation(hass) 113 | username = entry.options.get(CONF_USERNAME, entry.data.get(CONF_USERNAME)) 114 | password = entry.options.get(CONF_PASSWORD, entry.data.get(CONF_PASSWORD)) 115 | host = entry.options.get(CONF_HOST, entry.data.get(CONF_HOST)) 116 | SCAN_INTERVAL = timedelta(seconds=entry.options.get(CONF_POLLING_INTERVAL, 60)) 117 | session = async_get_clientsession(hass) 118 | system_type = entry.options.get(CONF_SYSTEMTYPE, entry.data.get(CONF_SYSTEMTYPE)) 119 | client = WaterkotteHeatpumpApiClient( 120 | host, username, password, session, tags, systemType=system_type 121 | ) 122 | if COORDINATOR is not None: 123 | coordinator = WaterkotteHeatpumpDataUpdateCoordinator( 124 | hass, 125 | client=client, 126 | data=COORDINATOR.data, 127 | lang=LANG, 128 | ) 129 | else: 130 | coordinator = WaterkotteHeatpumpDataUpdateCoordinator( 131 | hass, client=client, lang=LANG 132 | ) 133 | # await coordinator.async_refresh() ##Needed? 134 | 135 | if not coordinator.last_update_success: 136 | raise ConfigEntryNotReady 137 | 138 | hass.data[DOMAIN][entry.entry_id] = coordinator 139 | for platform in PLATFORMS: 140 | if entry.options.get(platform, True): 141 | coordinator.platforms.append(platform) 142 | # hass.async_add_job( 143 | await hass.config_entries.async_forward_entry_setup(entry, platform) 144 | # ) 145 | entry.add_update_listener(async_reload_entry) 146 | 147 | if len(tags) > 0: 148 | await coordinator.async_refresh() 149 | COORDINATOR = coordinator 150 | 151 | service = waterkotteservice.WaterkotteHeatpumpService(hass, entry, coordinator) 152 | 153 | hass.services.async_register(DOMAIN, "set_holiday", service.set_holiday) 154 | hass.services.async_register(DOMAIN, "get_energy_balance", service.get_energy_balance,supports_response=SupportsResponse.ONLY) 155 | hass.services.async_register(DOMAIN, "get_energy_balance_monthly", service.get_energy_balance_monthly,supports_response=SupportsResponse.ONLY) 156 | return True 157 | 158 | 159 | class WaterkotteHeatpumpDataUpdateCoordinator(DataUpdateCoordinator): 160 | """Class to manage fetching data from the API.""" 161 | 162 | def __init__( 163 | self, 164 | hass: HomeAssistant, 165 | client: WaterkotteHeatpumpApiClient, 166 | data=None, 167 | lang=None, 168 | ) -> None: 169 | """Initialize.""" 170 | self.api = client 171 | if data is None: 172 | self.data = {} 173 | else: 174 | self.data = data 175 | self.platforms = [] 176 | self.__hass = hass 177 | self.alltags = {} 178 | self.lang = lang 179 | super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) 180 | 181 | async def _async_update_data(self): 182 | """Update data via library.""" 183 | try: 184 | await self.api.login() 185 | if len(self.api.tags) == 0: 186 | tags = [] 187 | for entity in self.__hass.data["entity_registry"].entities: 188 | if ( # pylint: disable=line-too-long 189 | self.__hass.data["entity_registry"].entities[entity].platform 190 | == DOMAIN 191 | and self.__hass.data["entity_registry"] 192 | .entities[entity] 193 | .disabled 194 | is False 195 | ): 196 | # x += 1 197 | # print(entity) 198 | tag = ( 199 | self.__hass.data["entity_registry"] 200 | .entities[entity] 201 | .unique_id 202 | ) 203 | _LOGGER.debug(f"Entity: {entity} Tag: {tag.upper()}") 204 | # match = re.search(r"^.*\.(.*)_waterkotte_heatpump", entity) 205 | # match = re.search(r"^.*\.(.*)", entity) 206 | if tag is not None and tag.upper() in EcotouchTag.__members__: 207 | # print(match.groups()[0].upper()) 208 | if EcotouchTag[ # pylint: disable=unsubscriptable-object 209 | tag.upper() 210 | ]: 211 | # print(EcotouchTag[match.groups()[0].upper()]) # pylint: disable=unsubscriptable-object 212 | tags.append( 213 | EcotouchTag[tag.upper()] 214 | ) # pylint: disable=unsubscriptable-object 215 | # match = re.search(r"^.*\.(.*)", entity) 216 | # if match: 217 | # print(match.groups()[0].upper()) 218 | # if EcotouchTag[match.groups()[0].upper()]: # pylint: disable=unsubscriptable-object 219 | # # print(EcotouchTag[match.groups()[0].upper()]) # pylint: disable=unsubscriptable-object 220 | # tags.append(EcotouchTag[match.groups()[0].upper()]) # pylint: disable=unsubscriptable-object 221 | self.api.tags = tags 222 | 223 | tagdatas = await self.api.async_get_data() 224 | if self.data is None: 225 | self.data = {} 226 | for key in tagdatas: 227 | # print(f"{key}:{tagdatas[key]}") 228 | if tagdatas[key]["status"] == "S_OK": 229 | # self.data.update(tagdatas[key]) 230 | # self.data.update({key:tagdatas[key]}) 231 | self.data[key] = tagdatas[key] 232 | # self.data = 233 | return self.data 234 | except UpdateFailed as exception: 235 | raise UpdateFailed() from exception 236 | 237 | async def async_write_tag(self, tag: EcotouchTag, value): 238 | """Update single data""" 239 | _LOGGER.debug(f"async_write_tag: Writing Tag: {tag}, value: {value}") 240 | res = await self.api.async_write_value(tag, value) 241 | # print(res) 242 | _LOGGER.debug(f"async_write_tag: Result of writing Tag: {res}") 243 | try: 244 | #ntag=tag.tags[0] 245 | #val=res[ntag]["value"] 246 | #origval=self.data[tag]["value"] 247 | #self.data[tag]["value"]=val 248 | self.data[tag]["value"] = res[tag.tags[0]]["value"] 249 | except Exception as e: 250 | _LOGGER.debug(f"async_write_tag: EXCEPTION: {e}") 251 | # self.data[result[0]] 252 | 253 | async def async_read_values(self, tags: Sequence[EcotouchTag]) -> dict: 254 | """Get data from the API.""" 255 | ret = await self.api.async_read_values(tags) 256 | return ret 257 | 258 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 259 | """Handle removal of an entry.""" 260 | coordinator = hass.data[DOMAIN][entry.entry_id] 261 | 262 | unloaded = all( 263 | await asyncio.gather( 264 | *[ 265 | hass.config_entries.async_forward_entry_unload(entry, platform) 266 | for platform in PLATFORMS 267 | if platform in coordinator.platforms 268 | ] 269 | ) 270 | ) 271 | if unloaded: 272 | hass.data[DOMAIN].pop(entry.entry_id) 273 | 274 | await coordinator.api._client.logout() 275 | return unloaded 276 | 277 | 278 | async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: 279 | """Reload config entry.""" 280 | await async_unload_entry(hass, entry) 281 | await async_setup_entry(hass, entry) 282 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/number.py: -------------------------------------------------------------------------------- 1 | """Sensor platform for Waterkotte Heatpump.""" 2 | import logging 3 | 4 | # from homeassistant.helpers.entity import Entity, EntityCategory # , DeviceInfo 5 | from homeassistant.components.number import NumberEntity, NumberDeviceClass 6 | from homeassistant.helpers.typing import ConfigType, HomeAssistantType 7 | 8 | # from .const import DEFAULT_NAME 9 | 10 | 11 | # from .const import ICON 12 | # from .const import SENSOR 13 | 14 | # from .const import UnitOfTemperature 15 | 16 | 17 | # from homeassistant.const import ( 18 | # ATTR_ATTRIBUTION, 19 | # DEGREE, 20 | # DEVICE_CLASS_HUMIDITY, 21 | # DEVICE_CLASS_PRESSURE, 22 | # DEVICE_CLASS_TEMPERATURE, 23 | # LENGTH_KILOMETERS, 24 | # PRESSURE_HPA, 25 | # PRESSURE_BAR, 26 | # SPEED_KILOMETERS_PER_HOUR, 27 | # TEMP_CELSIUS, 28 | # TIME_SECONDS, 29 | # ) 30 | from pywaterkotte.ecotouch import EcotouchTag 31 | from .entity import WaterkotteHeatpumpEntity 32 | 33 | from .const import ENUM_OFFAUTOMANUAL, DEVICE_CLASS_ENUM, DOMAIN 34 | 35 | _LOGGER = logging.getLogger(__name__) 36 | _LANG = None 37 | 38 | # Sensor types are defined as: 39 | # variable -> [0]title, [1] EcoTouchTag, [2]device_class, [3]min, [4]icon, [5]enabled_by_default, [6]max, [7]step #pylint: disable=line-too-long 40 | SENSOR_TYPES = { 41 | "adapt_heating": [ 42 | "Temperature Heating Adapt", 43 | EcotouchTag.ADAPT_HEATING, 44 | NumberDeviceClass.TEMPERATURE, 45 | -2, 46 | "mdi:snowflake-thermometer", 47 | True, 48 | 2, 49 | 0.5, 50 | ], 51 | # "temperature_heating_set2": [ 52 | # "Temperature Heating Set2", 53 | # EcotouchTag.TEMPERATURE_HEATING_SET2, 54 | # NumberDeviceClass.TEMPERATURE, 55 | # 0, 56 | # "mdi:snowflake-thermometer", 57 | # True, 58 | # 100, 59 | # 0.1, 60 | # ], 61 | "nvi_soll_kuehlen": [ 62 | "Temperature Cooling Set", 63 | EcotouchTag.NVI_SOLL_KUEHLEN, 64 | NumberDeviceClass.TEMPERATURE, 65 | 5, 66 | "mdi:snowflake-thermometer", 67 | True, 68 | 26, 69 | 0.5, 70 | ], 71 | # "temperature_cooling_set2": [ 72 | # "Temperature Cooling Set2", 73 | # EcotouchTag.TEMPERATURE_COOLING_SET2, 74 | # NumberDeviceClass.TEMPERATURE, 75 | # 0, 76 | # "mdi:snowflake-thermometer", 77 | # True, 78 | # 100, 79 | # 0.1, 80 | # ], 81 | # "temperature_water_setpoint": [ 82 | # "Temperature Water Setpoint", 83 | # EcotouchTag.TEMPERATURE_WATER_SETPOINT, 84 | # NumberDeviceClass.TEMPERATURE, 85 | # 0, 86 | # "mdi:snowflake-thermometer", 87 | # True, 88 | # 100, 89 | # 0.1, 90 | # ], 91 | "temperature_water_setpoint2": [ 92 | "Temperature Water Setpoint2", 93 | EcotouchTag.TEMPERATURE_WATER_SETPOINT2, 94 | NumberDeviceClass.TEMPERATURE, 95 | 28, 96 | "mdi:snowflake-thermometer", 97 | True, 98 | 70, 99 | 0.5, 100 | ], 101 | "temperature_pool_setpoint": [ 102 | "Temperature Pool Setpoint", 103 | EcotouchTag.TEMPERATURE_POOL_SETPOINT, 104 | NumberDeviceClass.TEMPERATURE, 105 | 0, 106 | "mdi:snowflake-thermometer", 107 | False, 108 | 100, 109 | 0.1, 110 | ], 111 | "temperature_pool_setpoint2": [ 112 | "Temperature Pool Setpoint2", 113 | EcotouchTag.TEMPERATURE_POOL_SETPOINT2, 114 | DEVICE_CLASS_ENUM, 115 | 0, 116 | "mdi:snowflake-thermometer", 117 | False, 118 | 100, 119 | 0.1, 120 | ], 121 | "temperature_mixing1_set": [ 122 | "Temperature Mixing1 Set", 123 | EcotouchTag.TEMPERATURE_MIXING1_SET, 124 | NumberDeviceClass.TEMPERATURE, 125 | 0, 126 | "mdi:snowflake-thermometer", 127 | False, 128 | 100, 129 | 0.1, 130 | ], 131 | "temperature_mixing2_set": [ 132 | "Temperature Mixing2 Set", 133 | EcotouchTag.TEMPERATURE_MIXING2_SET, 134 | NumberDeviceClass.TEMPERATURE, 135 | 0, 136 | "mdi:snowflake-thermometer", 137 | False, 138 | 100, 139 | 0.1, 140 | ], 141 | "temperature_mixing3_set": [ 142 | "Temperature Mixing3 Set", 143 | EcotouchTag.TEMPERATURE_MIXING3_SET, 144 | NumberDeviceClass.TEMPERATURE, 145 | 0, 146 | "mdi:snowflake-thermometer", 147 | False, 148 | 100, 149 | 0.1, 150 | ], 151 | "adapt_mixing1": [ 152 | "Adapt Mixing1", 153 | EcotouchTag.ADAPT_MIXING1, 154 | NumberDeviceClass.TEMPERATURE, 155 | -2, 156 | "mdi:snowflake-thermometer", 157 | False, 158 | 2, 159 | 0.5, 160 | ], 161 | "adapt_mixing2": [ 162 | "Adapt Mixing2", 163 | EcotouchTag.ADAPT_MIXING2, 164 | NumberDeviceClass.TEMPERATURE, 165 | -2, 166 | "mdi:snowflake-thermometer", 167 | False, 168 | 2, 169 | 0.5, 170 | ], 171 | "adapt_mixing3": [ 172 | "Adapt Mixing3", 173 | EcotouchTag.ADAPT_MIXING3, 174 | NumberDeviceClass.TEMPERATURE, 175 | -2, 176 | "mdi:snowflake-thermometer", 177 | False, 178 | 2, 179 | 0.5, 180 | ], 181 | 182 | "t_heating_limit_mixing1": [ 183 | "T Heating limit Mixing 1", 184 | EcotouchTag.T_HEATING_LIMIT_MIXING1, 185 | NumberDeviceClass.TEMPERATURE, 186 | 0, 187 | None, 188 | False, 189 | 100, 190 | 0.1, 191 | ], 192 | "t_heating_limit_target_mixing1": [ 193 | "T Heating limit target Mixing 1", 194 | EcotouchTag.T_HEATING_LIMIT_TARGET_MIXING1, 195 | NumberDeviceClass.TEMPERATURE, 196 | 0, 197 | None, 198 | False, 199 | 100, 200 | 0.1, 201 | ], 202 | "t_norm_outdoor_mixing1": [ 203 | "T norm outdoor Mixing 1", 204 | EcotouchTag.T_NORM_OUTDOOR_MIXING1, 205 | NumberDeviceClass.TEMPERATURE, 206 | -15, 207 | None, 208 | False, 209 | 50, 210 | 0.1, 211 | ], 212 | "t_norm_heating_circle_mixing1": [ 213 | "T norm heating circle Mixing 1", 214 | EcotouchTag.T_HEATING_LIMIT_MIXING1, 215 | NumberDeviceClass.TEMPERATURE, 216 | 0, 217 | None, 218 | False, 219 | 100, 220 | 0.1, 221 | ], 222 | "max_temp_mixing1": [ 223 | "Max temperature in Mixing 1", 224 | EcotouchTag.MAX_TEMP_MIXING1, 225 | NumberDeviceClass.TEMPERATURE, 226 | 0, 227 | None, 228 | False, 229 | 100, 230 | 0.1, 231 | ], 232 | 233 | "t_heating_limit_mixing2": [ 234 | "T Heating limit Mixing 2", 235 | EcotouchTag.T_HEATING_LIMIT_MIXING2, 236 | NumberDeviceClass.TEMPERATURE, 237 | 0, 238 | None, 239 | False, 240 | 100, 241 | 0.1, 242 | ], 243 | "t_heating_limit_target_mixing2": [ 244 | "T Heating limit target Mixing 2", 245 | EcotouchTag.T_HEATING_LIMIT_TARGET_MIXING2, 246 | NumberDeviceClass.TEMPERATURE, 247 | 0, 248 | None, 249 | False, 250 | 100, 251 | 0.1, 252 | ], 253 | "t_norm_outdoor_mixing2": [ 254 | "T norm outdoor Mixing 2", 255 | EcotouchTag.T_NORM_OUTDOOR_MIXING2, 256 | NumberDeviceClass.TEMPERATURE, 257 | -15, 258 | None, 259 | False, 260 | 50, 261 | 0.1, 262 | ], 263 | "t_norm_heating_circle_mixing2": [ 264 | "T norm heating circle Mixing 2", 265 | EcotouchTag.T_HEATING_LIMIT_MIXING2, 266 | NumberDeviceClass.TEMPERATURE, 267 | 0, 268 | None, 269 | False, 270 | 100, 271 | 0.1, 272 | ], 273 | "max_temp_mixing2": [ 274 | "Max temperature in Mixing 2", 275 | EcotouchTag.MAX_TEMP_MIXING2, 276 | NumberDeviceClass.TEMPERATURE, 277 | 0, 278 | None, 279 | False, 280 | 100, 281 | 0.1, 282 | ], 283 | 284 | "t_heating_limit_mixing3": [ 285 | "T Heating limit Mixing 3", 286 | EcotouchTag.T_HEATING_LIMIT_MIXING3, 287 | NumberDeviceClass.TEMPERATURE, 288 | 0, 289 | None, 290 | False, 291 | 100, 292 | 0.1, 293 | ], 294 | "t_heating_limit_target_mixing3": [ 295 | "T Heating limit target Mixing 3", 296 | EcotouchTag.T_HEATING_LIMIT_TARGET_MIXING3, 297 | NumberDeviceClass.TEMPERATURE, 298 | 0, 299 | None, 300 | False, 301 | 100, 302 | 0.1, 303 | ], 304 | "t_norm_outdoor_mixing3": [ 305 | "T norm outdoor Mixing 3", 306 | EcotouchTag.T_NORM_OUTDOOR_MIXING3, 307 | NumberDeviceClass.TEMPERATURE, 308 | -15, 309 | None, 310 | False, 311 | 50, 312 | 0.1, 313 | ], 314 | "t_norm_heating_circle_mixing3": [ 315 | "T norm heating circle Mixing 3", 316 | EcotouchTag.T_HEATING_LIMIT_MIXING3, 317 | NumberDeviceClass.TEMPERATURE, 318 | 0, 319 | None, 320 | False, 321 | 100, 322 | 0.1, 323 | ], 324 | "max_temp_mixing3": [ 325 | "Max temperature in Mixing 3", 326 | EcotouchTag.MAX_TEMP_MIXING3, 327 | NumberDeviceClass.TEMPERATURE, 328 | 0, 329 | None, 330 | False, 331 | 100, 332 | 0.1, 333 | ], 334 | } 335 | 336 | ADAPT_LOOKUP = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2] 337 | 338 | 339 | async def async_setup_entry( 340 | hass: HomeAssistantType, entry: ConfigType, async_add_devices 341 | ) -> None: 342 | """Set up the Waterkotte sensor platform.""" 343 | # hass_data = hass.data[DOMAIN][entry.entry_id] 344 | _LOGGER.debug("Sensor async_setup_entry") 345 | coordinator = hass.data[DOMAIN][entry.entry_id] 346 | global _LANG 347 | _LANG = coordinator.lang 348 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_condensation")]) 349 | # async_add_devices([WaterkotteHeatpumpSensor(entry, coordinator, "temperature_evaporation")]) 350 | async_add_devices( 351 | [ 352 | WaterkotteHeatpumpSelect(entry, coordinator, sensor_type) 353 | for sensor_type in SENSOR_TYPES 354 | ] 355 | ) 356 | 357 | 358 | class WaterkotteHeatpumpSelect(NumberEntity, WaterkotteHeatpumpEntity): 359 | """waterkotte_heatpump Sensor class.""" 360 | 361 | def __init__( 362 | self, entry, hass_data, sensor_type 363 | ): # pylint: disable=unused-argument 364 | """Initialize the sensor.""" 365 | self._coordinator = hass_data 366 | 367 | self._type = sensor_type 368 | # self._name = f"{SENSOR_TYPES[self._type][0]} {DOMAIN}" 369 | self._name = f"{SENSOR_TYPES[self._type][0]}" 370 | self._name = _LANG[SENSOR_TYPES[self._type][1].tags[0]] 371 | # self._unique_id = f"{SENSOR_TYPES[self._type][0]}_{DOMAIN}" 372 | self._unique_id = self._type 373 | self._entry_data = entry.data 374 | self._device_id = entry.entry_id 375 | hass_data.alltags.update({self._unique_id: SENSOR_TYPES[self._type][1]}) 376 | super().__init__(hass_data, entry) 377 | 378 | @property 379 | def name(self): 380 | """Return the name of the sensor.""" 381 | return self._name 382 | 383 | @property 384 | def native_value(self) -> float | None: 385 | try: 386 | sensor = SENSOR_TYPES[self._type] 387 | value = self._coordinator.data[sensor[1]]["value"] 388 | if value is None or value == "": 389 | return "unknown" 390 | if self._type in ("adapt_heating","adapt_mixing1","adapt_mixing2","adapt_mixing3"): 391 | value = ADAPT_LOOKUP[value] 392 | except KeyError: 393 | return "unknown" 394 | except TypeError: 395 | return None 396 | return float(value) 397 | # return "auto" 398 | 399 | # @property 400 | # def native_uni(self) -> str: 401 | # # return ["off", "auto", "manual"] 402 | # return SENSOR_TYPES[self._type][6] 403 | 404 | async def async_set_native_value(self, value: float) -> None: 405 | """Turn on the switch.""" 406 | try: 407 | # print(option) 408 | # await self._coordinator.api.async_write_value(SENSOR_TYPES[self._type][1], option) 409 | if self._type in ("adapt_heating","adapt_mixing1","adapt_mixing2","adapt_mixing3"): 410 | value = ADAPT_LOOKUP.index(value) 411 | await self._coordinator.async_write_tag(SENSOR_TYPES[self._type][1], value) 412 | except ValueError: 413 | return "unavailable" 414 | 415 | @property 416 | def tag(self): 417 | """Return a unique ID to use for this entity.""" 418 | return SENSOR_TYPES[self._type][1] 419 | 420 | @property 421 | def icon(self): 422 | """Return the icon of the sensor.""" 423 | return SENSOR_TYPES[self._type][4] 424 | # return ICON 425 | 426 | @property 427 | def device_class(self): 428 | """Return the device class of the sensor.""" 429 | return SENSOR_TYPES[self._type][2] 430 | 431 | @property 432 | def entity_registry_enabled_default(self): 433 | """Return the entity_registry_enabled_default of the sensor.""" 434 | return SENSOR_TYPES[self._type][5] 435 | 436 | @property 437 | def native_step(self): 438 | """Return the native Step.""" 439 | try: 440 | return SENSOR_TYPES[self._type][7] 441 | except IndexError: 442 | return None 443 | 444 | @property 445 | def unique_id(self): 446 | """Return the unique of the sensor.""" 447 | return self._unique_id 448 | 449 | @property 450 | def native_min_value(self): 451 | """Return the minimum value.""" 452 | return SENSOR_TYPES[self._type][3] 453 | 454 | @property 455 | def native_max_value(self): 456 | """Return the maximum value.""" 457 | return SENSOR_TYPES[self._type][6] 458 | 459 | async def async_update(self): 460 | """Schedule a custom update via the common entity update service.""" 461 | await self._coordinator.async_request_refresh() 462 | 463 | @property 464 | def should_poll(self) -> bool: 465 | """Entities do not individually poll.""" 466 | return False 467 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/translations/heatpump.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": "Bitte richten Sie Ihren Account ein", 3 | "activateService": "Möchten Sie diesen Dienst aktivieren?", 4 | "alarm": "Meldungen", 5 | "allDay": "ganztägig", 6 | "assist": "Einrichtungsassistent", 7 | "assist1": "Bitte geben Sie die IP-Adresse Ihrer Wärmepumpe, Ihren Benutzernamen und Ihr Passwort ein.", 8 | "assist2": "Bitte geben Sie die IP-Adresse Ihrer Wärmepumpe an.", 9 | "assist3": "Sie können sich nun mit Ihrer Wärmepumpe verbinden", 10 | "assist5": "Folgende Wärmepumpe(n) sind für Sie registriert", 11 | "assist6": "Bitte loggen Sie sich in Ihren Account ein!", 12 | "assist7": "...oder zur Registrierung geht es hier:", 13 | "assist8": "Konfiguration gespeichert", 14 | "attention": "Achtung!", 15 | "autoStart": "automatisch Verbinden", 16 | "balanceSheet": "Jahresbilanz", 17 | "bypass": "Bypass", 18 | "boost": "Boost", 19 | "cancel": "abbrechen", 20 | "cfg": "Konfiguration", 21 | "cfgRBX": "Remotebox einrichten", 22 | "cfrmCopy": "Zeiten übernehmen?", 23 | "closed": "Zu", 24 | "club": "Waterkotte Club", 25 | "coffeetime": "Dies dauert einen Moment!", 26 | "comm": "Kommunikation", 27 | "comp": "Verdichter", 28 | "connect": "Verbinden", 29 | "contact": "Kontakt", 30 | "cool": "Kühlen", 31 | "cop": "COP", 32 | "coppower": "Leistungsbilanz", 33 | "curve": "Kennlinie", 34 | "day": "Tag", 35 | "decrease": "Absenkung", 36 | "default": "Standard", 37 | "defaultValues": "Standardwerte", 38 | "delete": "Löschen", 39 | "deleteConnection": "Möchten Sie folgende Verbindungen löschen?", 40 | "deleteData": "Daten löschen", 41 | "deleteDataDesc": "Ihre gespeicherten Daten werden dadurch gelöscht!", 42 | "deleteDataDone": "Daten wurden gelöscht", 43 | "deletePatience": "Die WATERKOTTE Wärmepumpe wird gerade gelöscht. Dieser Vorgang nimmt etwas Zeit in Anspruch.", 44 | "demo": "Demo", 45 | "demoInfo": "Trennen Sie bitte zuerst die Verbindung zur Wärmepumpe!", 46 | "demoInfo2": "Demo Modus läuft bereits!", 47 | "demoStart": "Demo Modus startet", 48 | "demoStop": "Demo beenden", 49 | "details": "Details", 50 | "deviceNotFound": "Gerät wurde nicht gefunden! Bitte überprüfen Sie Ihre Einstellungen.", 51 | "dhw": "Warmwasser", 52 | "disabled": "Inaktiv", 53 | "disconnect": "Trennen", 54 | "dsgvo": "Datenschutzerklärung", 55 | "ecovent": "EcoVent", 56 | "electricalWork": "Elektrische Arbeit", 57 | "email": "Email Adresse", 58 | "enabled": "Aktiv", 59 | "error": "Fehler", 60 | "errorAddHP1": "Das Anlegen ist fehlgeschlagen, bitte versuchen Sie es erneut", 61 | "errorAddHP2": "Es ist bereits eine Wärmepumpe im System angelegt.", 62 | "exit": "Beenden", 63 | "extended": "Zusatzfunktionen", 64 | "failure": "Ausfall", 65 | "findRBX": "Ihre Remotebox wird im Netzwerk gesucht", 66 | "friday": "Freitag", 67 | "hday": "Urlaubsfunktion", 68 | "holiday": "Urlaub", 69 | "heat": "Heizen", 70 | "heatCircuit": "T Heizkreis", 71 | "heater": "Heizstab", 72 | "heating": "Heizung", 73 | "heatpump": "Wärmepumpe", 74 | "heatsrcpump": "Quellenpumpe", 75 | "hellYeah": "Hell Yeah!!!", 76 | "help": "Hilfe", 77 | "helpCFG1": "Hier können Sie Ihre Daten eingeben und/oder überprüfen", 78 | "helpCFG2": "Nach dem Speichern können Sie sich mit Ihrer Wärmepumpe verbinden", 79 | "helpRBX1": "Ihr Smartphone und Ihre Remotebox müssen sich im selben Netzwerk (WLAN) befinden.", 80 | "helpRBX2": "Die RemoteBox muss angeschlossen und eingeschaltet sein.", 81 | "helpRBX3": "Die Installationsanleitung für Ihre Remotebox finden Sie hier:", 82 | "hh2o": "Wasser", 83 | "history": "Historie", 84 | "increase": "Anhebung", 85 | "inetOffline": "Sie sind offline", 86 | "influence": "Raumeinfluss", 87 | "info": "Information", 88 | "ipAddress": "IP Adresse", 89 | "ipaRBX": "IP Adresse Remotebox", 90 | "language": "Sprache", 91 | "letsgo": "Los geht's", 92 | "login": "LogIn", 93 | "lowerTLimit": "untere Temperaturbegrenzung", 94 | "macAddress": "Mac Adresse", 95 | "messages": "Meldungen", 96 | "mix": "Mischerkreis", 97 | "modeHeat": "Heizbetrieb", 98 | "modeHh2o": "Warmwasserbetrieb", 99 | "modePool": "Pool-Heizbetrieb", 100 | "monday": "Montag", 101 | "monitoring": "Monitoring", 102 | "moFr": "Mo-Fr", 103 | "moSu": "Mo-So", 104 | "month_0": "Januar", 105 | "month_0s": "Jan", 106 | "month_1": "Februar", 107 | "month_10": "November", 108 | "month_10s": "Nov", 109 | "month_11": "Dezember", 110 | "month_11s": "Dez", 111 | "month_1s": "Feb", 112 | "month_2": "März", 113 | "month_2s": "Mrz", 114 | "month_3": "April", 115 | "month_3s": "Apr", 116 | "month_4": "Mai", 117 | "month_4s": "Mai", 118 | "month_5": "Juni", 119 | "month_5s": "Jun", 120 | "month_6": "Juli", 121 | "month_6s": "Jul", 122 | "month_7": "August", 123 | "month_7s": "Aug", 124 | "month_8": "September", 125 | "month_8s": "Sep", 126 | "month_9": "Oktober", 127 | "month_9s": "Okt", 128 | "monthlyBalance": "Monatsbilanz", 129 | "mvalues": "Messwerte", 130 | "navDhw": "Wasser", 131 | "navHome": "Home", 132 | "navMore": "Mehr", 133 | "network": "Netzwerk", 134 | "new": "Neu", 135 | "next": "Weiter", 136 | "night": "Nacht", 137 | "noRBX": "Keine Remotebox eingerichtet", 138 | "notFoundHP": "Es ist noch keine Wärmepumpe im System angelegt.", 139 | "notFoundRBX": "Keine Remotebox im System gefunden", 140 | "off": "Aus", 141 | "offline": "Bitte verbinden sie sich zuerst mit einer Wärmepumpe", 142 | "ok": "OK", 143 | "okHP": "Ihre Wärmepumpe läuft fehlerfrei", 144 | "on": "An", 145 | "open": "Auf", 146 | "oph": "Betriebsstunden", 147 | "overview": "Übersicht", 148 | "patience": "Die WATERKOTTE Wärmepumpe wird gerade im System angelegt. Dieser Vorgang nimmt etwas Zeit in Anspruch.", 149 | "party": "Party", 150 | "play": "Verbindung wird hergestellt", 151 | "pool": "Pool", 152 | "prev": "Zurück", 153 | "privacy": "Datenschutz", 154 | "pv": "Photovoltaik", 155 | "pvTInc": "Temperaturänderung (während PV-Ertrag)", 156 | "pw_false": "falsches Passwort!", 157 | "pw_forgot": "Passwort vergessen?", 158 | "pw": "Passwort", 159 | "ready": "Fertig", 160 | "remotebox": "Remotebox", 161 | "saturday": "Samstag", 162 | "save": "Speichern", 163 | "saSu": "Sa-So", 164 | "searchDevice": "Gerät wird gesucht!", 165 | "select": "Bitte auswählen", 166 | "serviceDisabled": "Dieser Dienst ist nicht aktiviert!", 167 | "setpoint": "Sollwert", 168 | "settings": "Einstellungen", 169 | "setup": "Einrichten", 170 | "show": "Anzeigen", 171 | "showHP": "Wärmepumpe(n) anzeigen", 172 | "skip": "Überspringen", 173 | "solar": "Solar", 174 | "solarRegen": "Sonden Regenerierung", 175 | "start": "Beginn", 176 | "stop": "Ende", 177 | "stor": "Speicherentladepumpe", 178 | "successAddHP": "Die WATERKOTTE Wärmepumpe wurde erfolgreich angelegt!", 179 | "sunday": "Sonntag", 180 | "terminateHeat1": "Möchten Sie die Heizung wirklich ausschalten?", 181 | "terminateHeat2": "Der Frostschutz ist dann nicht mehr gewährleistet.", 182 | "terminateService": "Möchten Sie diesen Dienst deaktivieren?", 183 | "thermalDis": "Therm. Desinfektion", 184 | "thermalDisOpt0": "Kein", 185 | "thermalDisOpt1": "Tag", 186 | "thermalDisOpt2": "Alle", 187 | "thermalWork": "Thermische Arbeit", 188 | "thursday": "Donnerstag", 189 | "time": "Zeit", 190 | "timeTable": "Zeitprogramm", 191 | "total": "Gesamt", 192 | "tuesday": "Dienstag", 193 | "txtSuccessDelete": "Die WATERKOTTE Wärmepumpe wurde erfolgreich gelöscht!", 194 | "update": "Aktualisieren", 195 | "upperTLimit": "obere Temperaturbegrenzung", 196 | "user": "Benutzer", 197 | "userUnknown": "Benutzer unbekannt", 198 | "wednesday": "Mittwoch", 199 | "wizard1": "Ihre Wärmepumpe befindet sich im lokalen WLAN", 200 | "wizard2": "Sie möchten Ihre Wärmepumpe über den Waterkotte Club verbinden", 201 | "wizard3": "Lokal", 202 | "wizard4": "Testen Sie EasyCon Mobile im Demo-Modus!", 203 | "wizard5": "Sie möchten Ihre RemoteBox einrichten?", 204 | "wlan": "WLAN", 205 | "wpc": "Waterkotte Club", 206 | "A1": "Außentemperatur", 207 | "A2": "Außentemperatur 1h", 208 | "A3": "Außentemperatur 24h", 209 | "A4": "T Quelle Ein", 210 | "A5": "T Quelle Aus", 211 | "A6": "T Verdampfer", 212 | "A7": "T Saugleitung", 213 | "A8": "p Verdampfer", 214 | "A10": "T Sollwert", 215 | "A11": "T Rücklauf", 216 | "A12": "T Vorlauf", 217 | "A13": "T Kondensation", 218 | "A14": "Tc Bubble-Point", 219 | "A15": "p Kondensator", 220 | "A16": "Temperatur Pufferspeicher", 221 | "A17": "Temperatur Raum", 222 | "A18": "Temperatur Raum 1h", 223 | "A19": "Temperatur Warmwasser", 224 | "A20": "aktuelle Temperatur", 225 | "A21": "T Solar", 226 | "A22": "Austrittstemperatur Solarkollektor", 227 | "A23": "EEV Ventilöffnung", 228 | "A24": "Sauggas Überhitzung", 229 | "A25": "Elektrische Leistung", 230 | "A26": "Thermische Leistung", 231 | "A27": "Kälteleistung", 232 | "A28": "COP", 233 | "A29": "COP Kälteleistung", 234 | "A30": "aktuelle Temperatur", 235 | "A31": "geforderte Temperatur", 236 | "A32": "Heiztemperatur", 237 | "A33": "aktuelle Temperatur", 238 | "A34": "geforderte Temperatur", 239 | "A37": "aktuelle Temperatur", 240 | "A38": "geforderte Temperatur", 241 | "A40": "geforderte Temperatur", 242 | "A41": "Pool-Temperatur Sollwert", 243 | "A44": "aktuelle Temperatur Kreis 1", 244 | "A45": "geforderte Temperatur Kreis 1", 245 | "A46": "aktuelle Temperatur Kreis 2", 246 | "A47": "geforderte Temperatur Kreis 2", 247 | "A48": "aktuelle Temperatur Kreis 3", 248 | "A49": "geforderte Temperatur Kreis 3", 249 | "A51": "Drehzahl Heizungspumpe", 250 | "A52": "Drehzahl Quellenpumpe", 251 | "A58": "Leistung Verdichter", 252 | "A61": "Schaltdifferenz Sollwert", 253 | "A90": "T Außen 1h", 254 | "A91": "T Norm-Außen", 255 | "A92": "T Heizkreis Norm", 256 | "A93": "T Heizgrenze", 257 | "A94": "T Heizgrenze Soll", 258 | "A95": "Grenze für Sollwert (Max.)", 259 | "A96": "Temperatur Soll", 260 | "A98": "Raumtemperatur 1h", 261 | "A100": "Raumtemperatur Soll", 262 | "A107": "Schaltdifferenz Sollwert", 263 | "A108": "T Außen Einsatzgrenze", 264 | "A109": "Kühltemperatur", 265 | "A139": "Schaltdifferenz Sollwert", 266 | "A168": "geforderte Temperatur", 267 | "A174": "Schaltdifferenz Sollwert", 268 | "A205": "Einschalttemperaturdifferenz", 269 | "A206": "Ausschalttemperaturdifferenz", 270 | "A207": "Maximale Kollektortemperatur", 271 | "A274": "T Norm-Außen Mischerkreis 1", 272 | "A275": "T Heizkreis Norm Mischerkreis 1", 273 | "A276": "T Heizgrenze Mischerkreis 1", 274 | "A277": "T Heizgrenze Soll Mischerkreis 1", 275 | "A278": "Maximale Temperatur im Mischerkreis 1", 276 | "A286": "T Außen Einsatzgrenze", 277 | "A287": "Kühltemperatur", 278 | "A288": "Minimale Temperatur im Mischerkreis", 279 | "A320": "T Norm-Außen Mischerkreis 2", 280 | "A321": "T Heizkreis Norm Mischerkreis 2", 281 | "A322": "T Heizgrenze Mischerkreis 2", 282 | "A323": "T Heizgrenze Soll Mischerkreis 2", 283 | "A324": "Maximale Temperatur im Mischerkreis 2", 284 | "A332": "T Außen Einsatzgrenze", 285 | "A333": "Kühltemperatur", 286 | "A334": "Minimale Temperatur im Mischerkreis", 287 | "A366": "T Norm-Außen Mischerkreis 3", 288 | "A367": "T Heizkreis Norm Mischerkreis 3", 289 | "A368": "T Heizgrenze Mischerkreis 3", 290 | "A369": "T Heizgrenze Soll Mischerkreis 3", 291 | "A372": "Maximale Temperatur im Mischerkreis 3", 292 | "A378": "T Außen Einsatzgrenze", 293 | "A379": "Kühltemperatur", 294 | "A380": "Minimale Temperatur im Mischerkreis", 295 | "A416": "Raumtemperatur Sollwert während Abwesenheit", 296 | "A417": "Sollwertabsenkung Heizkreis während Abwesenheit", 297 | "A412": "aktuelle Temperatur", 298 | "A413": "Raumthermostat", 299 | "A414": "Schaltdifferenz Sollwert", 300 | "A415": "geforderte Temperatur", 301 | "A434": "Arbeitszahl Wärmepumpe", 302 | "A460": "COP Wärmepumpe", 303 | "A461": "COP Gesamtsystem", 304 | "A516": "Verdichter", 305 | "A518": "Außeneinheit", 306 | "A522": "Heizungspumpe", 307 | "A528": "Externer Wärmeerzeuger", 308 | "A530": "Heizbetrieb", 309 | "A532": "Kühlbetrieb", 310 | "A534": "Warmwasserbetrieb", 311 | "A536": "Pool-Heizbetrieb", 312 | "A538": "Solarbetrieb", 313 | "A540": "Funktionsheizbetrieb", 314 | "A542": "Abtauvorgang", 315 | "A544": "Bivalent parallel", 316 | "A546": "Bivalent alternativ", 317 | "A682": "...im Heizkreis", 318 | "A683": "...im Kühlkreis", 319 | "A684": "...im Warmwasserspeicher", 320 | "A685": "...im Pool", 321 | "A686": "Sondentemperatur", 322 | "A687": "Max. Sonden Temperatur", 323 | "A688": "Schaltdifferenz max. Temperatur", 324 | "A746": "T Außen 1h", 325 | "A747": "T Norm-Außen", 326 | "A748": "T Heizkreis Norm", 327 | "A749": "T Heizgrenze", 328 | "A750": "T Heizgrenze Soll", 329 | "A752": "Temperatur Soll", 330 | "A1014": "Spreizung Heizung", 331 | "A1035": "Spreizung Quelle", 332 | "A1094": "...im Mischerkreis", 333 | "A1095": "...im Mischerkreis 2", 334 | "A1096": "...im Mischerkreis 3", 335 | "A1101": "Eintrittstemperatur Solarkollektor", 336 | "A1194": "15 Min.-Mittelwert der Netzeinspeisung", 337 | "A1220": "Grenzwert Wärmequellenaustritt", 338 | "A1224": "Einschaltschwelle", 339 | "A1534": "Netzeinspeisung", 340 | "A1536": "PV-Überschuss", 341 | "D84": "Betriebsmodus", 342 | "D84_0": "Naturkühlung", 343 | "D84_1": "Umkehrkühlung", 344 | "D251": "Heizbetrieb", 345 | "D294": "Heizbetrieb", 346 | "D337": "Heizbetrieb", 347 | "D252": "Kühlbetrieb", 348 | "D295": "Kühlbetrieb", 349 | "D338": "Kühlbetrieb", 350 | "D421": "Kühlbetrieb blockieren", 351 | "D422": "Warmwasserbereitung blockieren", 352 | "D423": "Pool-Heizbetrieb blockieren", 353 | "D628": "Notbetrieb", 354 | "D1168": "Störung der Wärmepumpe", 355 | "D1168_0": "Notbetrieb", 356 | "D1168_1": "Frostschutz", 357 | "D1168_2": "*Der Einsatz des Heizstabes führt zu einem erhöhten Stromverbrauch", 358 | "I1": "Firmware", 359 | "I2": "Build", 360 | "I3": "BIOS", 361 | "I4": "BIOS Datum", 362 | "I5": "Datum", 363 | "I30": "Betriebsmodus", 364 | "I31": "Betriebsmodus", 365 | "I32": "Betriebsmodus", 366 | "I33": "Betriebsmodus", 367 | "I34": "Betriebsmodus", 368 | "I37": "Betriebsmodus", 369 | "I38": "Betriebsmodus", 370 | "I39": "Betriebsmodus", 371 | "I40": "Betriebsmodus", 372 | "I41": "Betriebsmodus", 373 | "I42": "Betriebsmodus", 374 | "I96": "IP-Adresse (WebInterface)", 375 | "I105": "Baureihe", 376 | "I108": "Steuermodus", 377 | "I109": "Außeneinheit", 378 | "I110": "ID", 379 | "I111": "Kältemittel", 380 | "I112": "Ausstattung", 381 | "I112_3": "Kombibetrieb", 382 | "I113": "Leistungsregelung", 383 | "I114": "Seriennummer", 384 | "I116": "Wärmequelle", 385 | "I116_0": "Wasser (Grundwasser)", 386 | "I116_1": "Wasser indirekt", 387 | "I116_2": "Erdreich (Sonden -3°C)", 388 | "I116_3": "Erdreich (Sonden -9°C)", 389 | "I116_4": "Eisspeicher", 390 | "I116_5": "Direktverdampfung", 391 | "I263": "Temperaturanpassung", 392 | "I264": "Raumeinfluss", 393 | "I505": "Startzeit", 394 | "I507": "max.Laufzeit", 395 | "I508": "Wochenprogramm", 396 | "I647_0": "geforderte min. Solarkollektortemperatur", 397 | "I647_1": "geforderte min. Solarkollektor Austrittstemperatur", 398 | "I776": "Temperaturanpassung", 399 | "I896": "Temperaturanpassung", 400 | "I1017": "Temperaturanpassung", 401 | "I1718": "Hardware", 402 | "I2079": "Geschwindigkeit", 403 | "I2101": "Kühlbetrieb", 404 | "I2205": "Software", 405 | "I2253": "Motorventil Warmwasser bei Sondenregenerierung", 406 | "I1475": "Protokoll", 407 | "I1476": "Geräteadresse", 408 | "I1740": "Temperaturanpassung", 409 | "I2020": "T2 Umgebung", 410 | "I2021": "T3 Sauggas", 411 | "I2022": "T4 Verdichter", 412 | "I2023": "T7 Ölsumpf", 413 | "I2024": "T6 Flüssig", 414 | "I2026": "Ventilöffnung Außeneinheit", 415 | "I2029": "Ventilöffnung Inneneinheit", 416 | "I2031": "Drehzahl", 417 | "I2033": "Überhitzung", 418 | "I2888": "Mittelwert" 419 | } 420 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/translations/heatpump.en.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": "Please create your account", 3 | "activateService": "Do you want to activate this service?", 4 | "alarm": "Notifications", 5 | "allDay": "All day", 6 | "assist": "Connection Wizard", 7 | "assist1": "Please enter the IP address and the associated port of your heatpump.", 8 | "assist2": "Please enter the IP address.", 9 | "assist3": "Now you can connect to your Heatpump!", 10 | "assist5": "The following heat pumps are registered for you", 11 | "assist6": "Please log in to your account!", 12 | "assist7": "...or to register click here", 13 | "assist8": "Configuration saved", 14 | "attention": "Attention!", 15 | "autoStart": "Autoconnect", 16 | "balanceSheet": "Yearly balance", 17 | "bypass": "Bypass", 18 | "boost": "Boost", 19 | "cancel": "cancel", 20 | "cfg": "Configuration", 21 | "cfgRBX": "Setup Remotebox", 22 | "cfrmCopy": "Apply times?", 23 | "closed": "Closed", 24 | "club": "Waterkotte Club", 25 | "coffeetime": "This takes a moment!", 26 | "comm": "Communication", 27 | "comp": "Compressor", 28 | "connect": "Connect", 29 | "contact": "Contact", 30 | "cool": "Cooling", 31 | "cop": "COP", 32 | "coppower": "Energy balance", 33 | "curve": "Heating curve", 34 | "day": "Day", 35 | "decrease": "Decrease", 36 | "default": "Standard", 37 | "defaultValues": "Default values", 38 | "delete": "Delete", 39 | "deleteConnection": "Do you want to delete the following connections?", 40 | "deleteData": "Delete data", 41 | "deleteDataDesc": "Your stored data will be deleted!", 42 | "deleteDataDone": "Data has been deleted", 43 | "deletePatience": "The WATERKOTTE heat pump is being deleted. This process takes some time.", 44 | "demo": "Demo", 45 | "demoInfo": "Please disconnect the connection to the heat pump first!", 46 | "demoInfo2": "Demo mode is already running!", 47 | "demoStart": "Demo mode starts", 48 | "demoStop": "Stop demo", 49 | "details": "Details", 50 | "deviceNotFound": "Device not found! Please check your Settings", 51 | "dhw": "Hot water", 52 | "disabled": "Inactiv", 53 | "disconnect": "Disconnect", 54 | "dsgvo": "Privacy Policy", 55 | "ecovent": "EcoVent", 56 | "electricalWork": "Electrical performance", 57 | "email": "Email Address", 58 | "enabled": "Activ", 59 | "error": "Error", 60 | "errorAddHP1": "The creation failed, please try again", 61 | "errorAddHP2": "A heat pump is already created in the system", 62 | "exit": "Exit", 63 | "extended": "Additional Functions", 64 | "failure": "Failures", 65 | "findRBX": "Your Remotebox is searched in the network", 66 | "friday": "Friday", 67 | "hday": "Holiday", 68 | "holiday": "Holiday", 69 | "heat": "Heating", 70 | "heatCircuit": "T Heating Circuit", 71 | "heater": "Electrical heater", 72 | "heating": "Heating", 73 | "heatpump": "Heat pump", 74 | "heatsrcpump": "Source pump", 75 | "hellYeah": "Hell Yeah!!!", 76 | "help": "Help", 77 | "helpCFG1": "Here you can enter and/or check your data", 78 | "helpCFG2": "After saving, you can connect to your heat pump", 79 | "helpRBX1": "Your smartphone and your remote box must be in the same network (WiFi).", 80 | "helpRBX2": "The remote box must be plugged in and powered up.", 81 | "helpRBX3": "You can find the installation instructions for your Remotebox here:", 82 | "hh2o": "Water", 83 | "history": "History", 84 | "increase": "Increase", 85 | "inetOffline": "You are offline", 86 | "influence": "Room influence", 87 | "info": "Information", 88 | "ipAddress": "IP address", 89 | "ipaRBX": "IP address Remotebox", 90 | "language": "Language", 91 | "letsgo": "Let's go", 92 | "login": "LogIn", 93 | "lowerTLimit": "lower temperature limit", 94 | "macAddress": "Mac address", 95 | "messages": "Notifications", 96 | "mix": "Mixing circle", 97 | "modeHeat": "Heating mode", 98 | "modeHh2o": "Hot water mode", 99 | "modePool": "Pool mode", 100 | "moFr": "Mo-Fr", 101 | "monday": "Monday", 102 | "monitoring": "Monitoring", 103 | "month_0": "January", 104 | "month_0s": "Jan", 105 | "month_1": "February", 106 | "month_10": "November", 107 | "month_10s": "Nov", 108 | "month_11": "December", 109 | "month_11s": "Dec", 110 | "month_1s": "Feb", 111 | "month_2": "March", 112 | "month_2s": "Mar", 113 | "month_3": "April", 114 | "month_3s": "Apr", 115 | "month_4": "May", 116 | "month_4s": "May", 117 | "month_5": "June", 118 | "month_5s": "Jun", 119 | "month_6": "July", 120 | "month_6s": "Jul", 121 | "month_7": "August", 122 | "month_7s": "Aug", 123 | "month_8": "September", 124 | "month_8s": "Sep", 125 | "month_9": "October", 126 | "month_9s": "Oct", 127 | "monthlyBalance": "Monthly balance", 128 | "moSu": "Mo-Su", 129 | "mvalues": "Measured values", 130 | "navDhw": "Water", 131 | "navHome": "Home", 132 | "navMore": "More", 133 | "night": "Night", 134 | "network": "Home network", 135 | "new": "New", 136 | "next": "Continue", 137 | "noRBX": "No Remotebox set up", 138 | "notFoundHP": "No heat pump has been created in the system yet", 139 | "notFoundRBX": "No Remotebox found in the system", 140 | "off": "Off", 141 | "offline": "Bitte verbinden sie sich zuerst mit einer Wärmepumpe", 142 | "ok": "OK", 143 | "okHP": "Your heat pump runs faultlessly", 144 | "on": "On", 145 | "open": "Open", 146 | "oph": "Operating hours", 147 | "overview": "Overview", 148 | "patience": "The WATERKOTTE heat pump is being created in the system. This process takes some time.", 149 | "party": "Party", 150 | "play": "Connecting", 151 | "pool": "Pool", 152 | "prev": "Back", 153 | "privacy": "Data Privacy", 154 | "pv": "Photovoltaic", 155 | "pvTInc": "Temperature increase (during PV income)", 156 | "pw_false": "Wrong password!", 157 | "pw_forgot": "Forgot password?", 158 | "pw": "Password", 159 | "ready": "Ready", 160 | "remotebox": "Remotebox", 161 | "saSu": "Sa-Su", 162 | "saturday": "Saturday", 163 | "save": "Save", 164 | "searchDevice": "Search for device", 165 | "select": "Please select", 166 | "serviceDisabled": "This service is not activated!", 167 | "setpoint": "Setpoint", 168 | "settings": "Settings", 169 | "setup": "Setup", 170 | "show": "View", 171 | "showHP": "show Heat pump(s)", 172 | "skip": "Skip", 173 | "solar": "Solar", 174 | "solarRegen": "Soil regeneration", 175 | "start": "Begin", 176 | "stop": "End", 177 | "stor": "Buffer discharge pump", 178 | "successAddHP": "The WATERKOTTE heat pump was successfully created!", 179 | "sunday": "Sunday", 180 | "terminateHeat1": "Do you really want to switch off the heating?", 181 | "terminateHeat2": "Frost protection is then no longer guaranteed", 182 | "terminateService": "Do you want to deactivate this service?", 183 | "thermalDis": "Thermal disinfection", 184 | "thermalDisOpt0": "None", 185 | "thermalDisOpt1": "Day", 186 | "thermalDisOpt2": "All", 187 | "thermalWork": "Thermal performance", 188 | "thursday": "Thursday", 189 | "time": "Time", 190 | "timeTable": "Time program", 191 | "total": "Total", 192 | "tuesday": "Tuesday", 193 | "txtSuccessDelete": "The WATERKOTTE heat pump was successfully deleted!", 194 | "update": "Update", 195 | "upperTLimit": "upper temperature limit", 196 | "user": "User", 197 | "userUnknown": "User unknown", 198 | "wednesday": "Wednesday", 199 | "wizard1": "Your heat pump is in the local WLAN", 200 | "wizard2": "You would like to connect your heat pump via the Waterkotte Club", 201 | "wizard3": "Local", 202 | "wizard4": "Try out EasyCon Mobile in demo mode!", 203 | "wizard5": "You want to set up your RemoteBox?", 204 | "wlan": "wlan", 205 | "wpc": "Waterkotte Club", 206 | "A1": "Outdoor temperature", 207 | "A2": "Outdoor temperature 1h", 208 | "A3": "Outdoor temperature 24h", 209 | "A4": "T source entry", 210 | "A5": "T source exit", 211 | "A6": "T evaporation", 212 | "A7": "T suction line", 213 | "A8": "p evaporation", 214 | "A10": "T setpoint", 215 | "A11": "T return", 216 | "A12": "T flow", 217 | "A13": "T condensation", 218 | "A14": "T Bubble Point", 219 | "A15": "p condensation", 220 | "A16": "Temperature buffer tank", 221 | "A17": "Room temperature", 222 | "A18": "Room temperature 1h", 223 | "A19": "Hot water temperature", 224 | "A20": "Actual temperature", 225 | "A21": "T Solar", 226 | "A22": "Exit temperature solar collector", 227 | "A23": "Valve opening EEV", 228 | "A24": "suction gas overheating", 229 | "A25": "Electrical power", 230 | "A26": "Thermal power", 231 | "A27": "Cooling power", 232 | "A28": "COP", 233 | "A29": "COP cooling power", 234 | "A30": "Actual temperature", 235 | "A31": "Demanded temperature", 236 | "A32": "Heating temperature", 237 | "A33": "Actual temperature", 238 | "A34": "Demanded temperature", 239 | "A37": "Actual temperature", 240 | "A38": "Demanded temperature", 241 | "A40": "Demanded temperature", 242 | "A41": "Pool temperature nominal", 243 | "A44": "Actual temperature circle 1", 244 | "A45": "Demanded temperature circle 1", 245 | "A46": "Actual temperature circle 2", 246 | "A47": "Demanded temperature circle 2", 247 | "A48": "Actual temperature circle 3", 248 | "A49": "Demanded temperature circle 3", 249 | "A51": "Speed heating pump", 250 | "A52": "Speed source pump", 251 | "A58": "Power compressor", 252 | "A61": "Hysteresis setpoint", 253 | "A90": "T outdoor 1h", 254 | "A91": "T norm outdoor", 255 | "A92": "T norm heating circle", 256 | "A93": "T heating limit", 257 | "A94": "T heating limit target", 258 | "A95": "Limit for setpoint (Max.)", 259 | "A96": "Temperature set", 260 | "A98": "Room temperature 1h", 261 | "A100": "Room temp. target", 262 | "A107": "Hysteresis setpoint", 263 | "A108": "T out begin", 264 | "A109": "T Cooling", 265 | "A139": "Hysteresis setpoint", 266 | "A168": "Demanded temperature", 267 | "A174": "Hysteresis setpoint", 268 | "A205": "Switch on temperature difference", 269 | "A206": "Switch off temperature difference", 270 | "A207": "Maximum collector temperature", 271 | "A274": "T norm outdoor circle 1", 272 | "A275": "T norm heating circle 1", 273 | "A276": "T heating limit circle 1", 274 | "A277": "T heating limit target circle 1", 275 | "A278": "Max. temperature in mixing circle 1", 276 | "A286": "T outdoor operation limit", 277 | "A287": "Cooling temperature", 278 | "A288": "Min. temperature in mixing circle", 279 | "A320": "T norm outdoor circle 2", 280 | "A321": "T norm heating circle 2", 281 | "A322": "T heating limit circle 2", 282 | "A323": "T heating limit target circle 2", 283 | "A324": "Max. temperature in mixing circle 2", 284 | "A332": "T outdoor operation limit", 285 | "A333": "Cooling temperature", 286 | "A334": "Min. temperature in mixing circle", 287 | "A366": "T norm outdoor circle 3", 288 | "A367": "T norm heating circle 3", 289 | "A368": "T heating limit circle 3", 290 | "A369": "T heating limit target circle 3", 291 | "A372": "Max. temperature in mixing circle 3", 292 | "A378": "T outdoor operation limit", 293 | "A379": "Cooling temperature", 294 | "A380": "Min. temperature in mixing circle", 295 | "A416": "Room temperature setpoint during absence", 296 | "A417": "Heating circuit setpoint lowering during absence", 297 | "A412": "Actual temperature", 298 | "A413": "Room thermostat", 299 | "A414": "Hysteresis setpoint", 300 | "A415": "Demanded temperature", 301 | "A434": "COP heat pump", 302 | "A460": "COP heat pump", 303 | "A461": "COP total system", 304 | "A516": "Compressor", 305 | "A518": "Outdoor unit", 306 | "A522": "Heating pump", 307 | "A528": "External heating", 308 | "A530": "Heating mode", 309 | "A532": "Cooling mode", 310 | "A534": "Hot water mode", 311 | "A536": "Pool mode", 312 | "A538": "Solar mode", 313 | "A540": "Functional heating mode", 314 | "A542": "Defrost", 315 | "A544": "Bivalent parallel", 316 | "A546": "Bivalent alternative", 317 | "A682": "...in the heating circuit", 318 | "A683": "...in the cooling circuit", 319 | "A684": "...in the hot water buffer", 320 | "A685": "...in the pool", 321 | "A686": "Probe temperature", 322 | "A687": "Max. probe temperature", 323 | "A688": "Hysteresis temperature", 324 | "A746": "T outdoor 1h", 325 | "A747": "T norm outdoor", 326 | "A748": "T norm heating circle", 327 | "A749": "T heating limit", 328 | "A750": "T heating limit target", 329 | "A752": "Temperature set", 330 | "A1014": "ΔT", 331 | "A1035": "ΔT", 332 | "A1094": "...in the mixing circle", 333 | "A1095": "...in the mixing circle 2", 334 | "A1096": "...in the mixing circle 3", 335 | "A1101": "Entry temperature solar collector", 336 | "A1194": "15 Min.-Average value of power supply", 337 | "A1220": "Limitation value heat source exit", 338 | "A1224": "Switch-on limitation value", 339 | "A1534": "Injection", 340 | "A1536": "PV Oversupply", 341 | "D84": "Operation mode", 342 | "D84_0": "NC - Passive cooling", 343 | "D84_1": "RC - Reverse cooling", 344 | "D251": "Heating mode", 345 | "D294": "Heating mode", 346 | "D337": "Heating mode", 347 | "D252": "Cooling mode", 348 | "D295": "Cooling mode", 349 | "D338": "Cooling mode", 350 | "D421": "Disable cooling mode", 351 | "D422": "Disable hot water mode", 352 | "D423": "Disable pool mode", 353 | "D628": "Emergency operation", 354 | "D1168": "Heat pump failure", 355 | "D1168_0": "Emergency operation", 356 | "D1168_1": "Frost protection", 357 | "D1168_2": "*The use of the electrical heater causes a higher power consumption", 358 | "I1": "Firmware", 359 | "I2": "Build", 360 | "I3": "BIOS", 361 | "I4": "BIOS date", 362 | "I5": "Date", 363 | "I30": "Operation mode", 364 | "I31": "Operation mode", 365 | "I32": "Operation mode", 366 | "I33": "Operation mode", 367 | "I34": "Operation mode", 368 | "I37": "Operation mode", 369 | "I38": "Operation mode", 370 | "I39": "Operation mode", 371 | "I40": "Operation mode", 372 | "I41": "Operation mode", 373 | "I42": "Operation mode", 374 | "I96": "IP-address (WebInterface)", 375 | "I105": "Series", 376 | "I108": "Control mode", 377 | "I109": "Outdoor unit", 378 | "I110": "ID", 379 | "I111": "Refrigerant", 380 | "I112": "Equipment", 381 | "I112_3": "Combined Operatiom", 382 | "I113": "Power control", 383 | "I114": "Serial number", 384 | "I116": "Heat source", 385 | "I116_0": "Water (Ground water)", 386 | "I116_1": "Water indirect", 387 | "I116_2": "Earth (Probes -3°C)", 388 | "I116_3": "Earth (Probes -9°C)", 389 | "I116_4": "Ice storage", 390 | "I116_5": "Direct evaporation", 391 | "I263": "Temperature adjustment", 392 | "I264": "Room influence", 393 | "I505": "Start time", 394 | "I507": "Max. runtime", 395 | "I508": "Weekly program", 396 | "I647_0": "Required min. temperature solar collector", 397 | "I647_1": "Required min. exit temperature solar collector", 398 | "I776": "Temperature adjustment", 399 | "I896": "Temperature adjustment", 400 | "I1017": "Temperature adjustment", 401 | "I1718": "Hardware", 402 | "I2079": "Velocity", 403 | "I2101": "Cooling mode", 404 | "I2205": "Software", 405 | "I2253": "Motorvalve hot water at probe regeneration", 406 | "I1475": "Protocol", 407 | "I1476": "Device address", 408 | "I1740": "Temperature adjustment", 409 | "I2020": "T2 environment", 410 | "I2021": "T3 suction gas", 411 | "I2022": "T4 compressor", 412 | "I2023": "T7 oilsump", 413 | "I2024": "T6 liquid", 414 | "I2026": "Valve opening outdoor unit", 415 | "I2029": "Valve opening indoor unit", 416 | "I2031": "Speed", 417 | "I2033": "Overheating", 418 | "I2888": "Mean value" 419 | } 420 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/sensor.py: -------------------------------------------------------------------------------- 1 | """Sensor platform for Waterkotte Heatpump.""" 2 | import logging 3 | 4 | # from homeassistant.helpers.entity import Entity, EntityCategory # , DeviceInfo 5 | from homeassistant.components.sensor import SensorEntity 6 | from homeassistant.helpers.typing import ConfigType, HomeAssistantType 7 | 8 | # from .const import DEFAULT_NAME 9 | 10 | 11 | # from .const import ICON 12 | # from .const import SENSOR 13 | 14 | # from .const import UnitOfTemperature 15 | 16 | 17 | from homeassistant.const import ( 18 | DEVICE_CLASS_DATE, 19 | DEVICE_CLASS_POWER_FACTOR, 20 | PERCENTAGE, 21 | DEVICE_CLASS_PRESSURE, 22 | DEVICE_CLASS_TEMPERATURE, 23 | PRESSURE_BAR, 24 | TEMP_CELSIUS, 25 | DEVICE_CLASS_ENERGY, 26 | DEVICE_CLASS_POWER 27 | ) 28 | from pywaterkotte.ecotouch import EcotouchTag 29 | from .entity import WaterkotteHeatpumpEntity 30 | 31 | from .const import DOMAIN 32 | 33 | _LOGGER = logging.getLogger(__name__) 34 | 35 | 36 | # Sensor types are defined as: 37 | # variable -> [0]title, [1] EcoTouchTag, [2]device_class, [3]units, [4]icon, [5]enabled_by_default, [6]options, [7]entity_category #pylint: disable=line-too-long 38 | SENSOR_TYPES = { 39 | "status_heating": [ 40 | "Status Heating", 41 | EcotouchTag.STATUS_HEATING, 42 | None, 43 | None, 44 | "mdi:weather-windy", 45 | True, 46 | None, 47 | None, 48 | ], 49 | "status_water": [ 50 | "Status Water", 51 | EcotouchTag.STATUS_WATER, 52 | None, 53 | None, 54 | "mdi:weather-rainy", 55 | True, 56 | None, 57 | None, 58 | ], 59 | "status_cooling": [ 60 | "Status Cooling", 61 | EcotouchTag.STATUS_COOLING, 62 | None, 63 | None, 64 | "mdi:weather-rainy", 65 | True, 66 | None, 67 | None, 68 | ], 69 | "temperature_outside": [ 70 | "Temperature Outside", 71 | EcotouchTag.TEMPERATURE_OUTSIDE, 72 | DEVICE_CLASS_TEMPERATURE, 73 | TEMP_CELSIUS, 74 | "mdi:thermometer", 75 | True, 76 | None, 77 | None, 78 | ], 79 | "temperature_outside_1h": [ 80 | "Temperature Outside 1h", 81 | EcotouchTag.TEMPERATURE_OUTSIDE_1H, 82 | DEVICE_CLASS_TEMPERATURE, 83 | TEMP_CELSIUS, 84 | "mdi:thermometer", 85 | True, 86 | None, 87 | None, 88 | ], 89 | "temperature_outside_24h": [ 90 | "Temperature Outside 24h", 91 | EcotouchTag.TEMPERATURE_OUTSIDE_24H, 92 | DEVICE_CLASS_TEMPERATURE, 93 | TEMP_CELSIUS, 94 | "mdi:thermometer", 95 | True, 96 | None, 97 | None, 98 | ], 99 | "temperature_source_in": [ 100 | "Temperature Source In", 101 | EcotouchTag.TEMPERATURE_SOURCE_IN, 102 | DEVICE_CLASS_TEMPERATURE, 103 | TEMP_CELSIUS, 104 | "mdi:thermometer", 105 | True, 106 | None, 107 | None, 108 | ], 109 | "temperature_source_out": [ 110 | "Temperature Source Out", 111 | EcotouchTag.TEMPERATURE_SOURCE_OUT, 112 | DEVICE_CLASS_TEMPERATURE, 113 | TEMP_CELSIUS, 114 | "mdi:thermometer", 115 | True, 116 | None, 117 | None, 118 | ], 119 | "temperature_evaporation": [ 120 | "Temperature Evaporation", 121 | EcotouchTag.TEMPERATURE_EVAPORATION, 122 | DEVICE_CLASS_TEMPERATURE, 123 | TEMP_CELSIUS, 124 | "mdi:thermometer", 125 | True, 126 | None, 127 | None, 128 | ], 129 | "temperature_suction": [ 130 | "Temperature Suction", 131 | EcotouchTag.TEMPERATURE_SUCTION, 132 | DEVICE_CLASS_TEMPERATURE, 133 | TEMP_CELSIUS, 134 | "mdi:thermometer", 135 | True, 136 | None, 137 | None, 138 | ], 139 | "temperature_return_set": [ 140 | "Temperature Return Set", 141 | EcotouchTag.TEMPERATURE_RETURN_SET, 142 | DEVICE_CLASS_TEMPERATURE, 143 | TEMP_CELSIUS, 144 | "mdi:thermometer", 145 | True, 146 | None, 147 | None, 148 | ], 149 | "temperature_return": [ 150 | "Temperature Return", 151 | EcotouchTag.TEMPERATURE_RETURN, 152 | DEVICE_CLASS_TEMPERATURE, 153 | TEMP_CELSIUS, 154 | "mdi:thermometer", 155 | True, 156 | None, 157 | None, 158 | ], 159 | "temperature_flow": [ 160 | "Temperature Flow", 161 | EcotouchTag.TEMPERATURE_FLOW, 162 | DEVICE_CLASS_TEMPERATURE, 163 | TEMP_CELSIUS, 164 | "mdi:thermometer", 165 | True, 166 | None, 167 | None, 168 | ], 169 | "temperature_condensation": [ 170 | "Temperature Condensation", 171 | EcotouchTag.TEMPERATURE_CONDENSATION, 172 | DEVICE_CLASS_TEMPERATURE, 173 | TEMP_CELSIUS, 174 | "mdi:thermometer", 175 | True, 176 | None, 177 | None, 178 | ], 179 | "temperature_storage": [ 180 | "Temperature Storage", 181 | EcotouchTag.TEMPERATURE_STORAGE, 182 | DEVICE_CLASS_TEMPERATURE, 183 | TEMP_CELSIUS, 184 | "mdi:thermometer", 185 | True, 186 | None, 187 | None, 188 | ], 189 | "temperature_room": [ 190 | "Temperature Room", 191 | EcotouchTag.TEMPERATURE_ROOM, 192 | DEVICE_CLASS_TEMPERATURE, 193 | TEMP_CELSIUS, 194 | "mdi:thermometer", 195 | False, 196 | None, 197 | None, 198 | ], 199 | "temperature_room_1h": [ 200 | "Temperature Room 1h", 201 | EcotouchTag.TEMPERATURE_ROOM_1H, 202 | DEVICE_CLASS_TEMPERATURE, 203 | TEMP_CELSIUS, 204 | "mdi:thermometer", 205 | False, 206 | None, 207 | None, 208 | ], 209 | "temperature_water": [ 210 | "Temperature Water", 211 | EcotouchTag.TEMPERATURE_WATER, 212 | DEVICE_CLASS_TEMPERATURE, 213 | TEMP_CELSIUS, 214 | "mdi:thermometer", 215 | True, 216 | None, 217 | None, 218 | ], 219 | "temperature_pool": [ 220 | "Temperature Pool", 221 | EcotouchTag.TEMPERATURE_POOL, 222 | DEVICE_CLASS_TEMPERATURE, 223 | TEMP_CELSIUS, 224 | "mdi:thermometer", 225 | False, 226 | None, 227 | None, 228 | ], 229 | "temperature_solar": [ 230 | "Temperature Solar", 231 | EcotouchTag.TEMPERATURE_SOLAR, 232 | DEVICE_CLASS_TEMPERATURE, 233 | TEMP_CELSIUS, 234 | "mdi:thermometer", 235 | False, 236 | None, 237 | None, 238 | ], 239 | "temperature_solar_flow": [ 240 | "Temperature Solar Flow", 241 | EcotouchTag.TEMPERATURE_SOLAR_FLOW, 242 | DEVICE_CLASS_TEMPERATURE, 243 | TEMP_CELSIUS, 244 | "mdi:thermometer", 245 | False, 246 | None, 247 | None, 248 | ], 249 | "temperature_heating_return": [ 250 | "Temperature Heating Return", 251 | EcotouchTag.TEMPERATURE_HEATING_RETURN, 252 | DEVICE_CLASS_TEMPERATURE, 253 | TEMP_CELSIUS, 254 | "mdi:thermometer", 255 | False, 256 | None, 257 | None, 258 | ], 259 | "temperature_cooling_return": [ 260 | "Temperature Cooling Return", 261 | EcotouchTag.TEMPERATURE_COOLING_RETURN, 262 | DEVICE_CLASS_TEMPERATURE, 263 | TEMP_CELSIUS, 264 | "mdi:thermometer", 265 | False, 266 | None, 267 | None, 268 | ], 269 | "temperature2_outside_1h": [ # TEMPERATURE2_OUTSIDE_1H = TagData(["A90"], "°C") 270 | "Temperature2 Outside 1h", 271 | EcotouchTag.TEMPERATURE2_OUTSIDE_1H, 272 | DEVICE_CLASS_TEMPERATURE, 273 | TEMP_CELSIUS, 274 | "mdi:thermometer", 275 | False, 276 | None, 277 | None, 278 | ], 279 | "pressure_evaporation": [ 280 | "Pressure Evaporation", 281 | EcotouchTag.PRESSURE_EVAPORATION, 282 | DEVICE_CLASS_PRESSURE, 283 | PRESSURE_BAR, 284 | "mdi:gauge", 285 | False, 286 | None, 287 | None, 288 | ], 289 | "pressure_condensation": [ 290 | "Pressure Condensation", 291 | EcotouchTag.PRESSURE_CONDENSATION, 292 | DEVICE_CLASS_PRESSURE, 293 | PRESSURE_BAR, 294 | "mdi:gauge", 295 | False, 296 | None, 297 | None, 298 | ], 299 | "position_expansion_valve": [ 300 | "Position Expansion Valve", 301 | EcotouchTag.POSITION_EXPANSION_VALVE, 302 | None, 303 | None, 304 | "mdi:gauge", 305 | False, 306 | None, 307 | None, 308 | ], 309 | "power_compressor": [ 310 | "Power Compressor", 311 | EcotouchTag.POWER_COMPRESSOR, 312 | DEVICE_CLASS_POWER, 313 | None, 314 | "mdi:gauge", 315 | False, 316 | None, 317 | None, 318 | ], 319 | "power_heating": [ 320 | "Power Heating", 321 | EcotouchTag.POWER_HEATING, 322 | DEVICE_CLASS_POWER, 323 | None, 324 | "mdi:gauge", 325 | False, 326 | None, 327 | None, 328 | ], 329 | "power_cooling": [ 330 | "Power Cooling", 331 | EcotouchTag.POWER_COOLING, 332 | DEVICE_CLASS_POWER, 333 | None, 334 | "mdi:gauge", 335 | False, 336 | None, 337 | None, 338 | ], 339 | "cop_heating": [ 340 | "COP Heating", 341 | EcotouchTag.COP_HEATING, 342 | None, 343 | None, 344 | "mdi:gauge", 345 | False, 346 | None, 347 | None, 348 | ], 349 | "cop_cooling": [ 350 | "COP Cooling", 351 | EcotouchTag.COP_COOLING, 352 | None, 353 | None, 354 | "mdi:gauge", 355 | False, 356 | None, 357 | None, 358 | ], 359 | "percent_heat_circ_pump": [ 360 | "Percent Heat Circ Pump", 361 | EcotouchTag.PERCENT_HEAT_CIRC_PUMP, 362 | DEVICE_CLASS_POWER_FACTOR, 363 | PERCENTAGE, 364 | "mdi:gauge", 365 | False, 366 | None, 367 | None, 368 | ], 369 | "percent_source_pump": [ 370 | "Percent Source Pump", 371 | EcotouchTag.PERCENT_SOURCE_PUMP, 372 | DEVICE_CLASS_POWER_FACTOR, 373 | PERCENTAGE, 374 | "mdi:gauge", 375 | False, 376 | None, 377 | None, 378 | ], 379 | "percent_compressor": [ 380 | "Percent Compressor", 381 | EcotouchTag.PERCENT_COMPRESSOR, 382 | DEVICE_CLASS_POWER_FACTOR, 383 | PERCENTAGE, 384 | "mdi:gauge", 385 | False, 386 | None, 387 | None, 388 | ], 389 | "holiday_start_time": [ 390 | "Holiday start", 391 | EcotouchTag.HOLIDAY_START_TIME, 392 | DEVICE_CLASS_DATE, 393 | None, 394 | "mdi:calendar-arrow-right", 395 | True, 396 | None, 397 | None, 398 | ], 399 | "holiday_end_time": [ 400 | "Holiday end", 401 | EcotouchTag.HOLIDAY_END_TIME, 402 | DEVICE_CLASS_DATE, 403 | None, 404 | "mdi:calendar-arrow-left", 405 | True, 406 | None, 407 | None, 408 | ], 409 | "state_service": [ 410 | "State Service", 411 | EcotouchTag.STATE_SERVICE, 412 | None, 413 | None, 414 | None, 415 | True, 416 | None, 417 | None, 418 | ], 419 | "temperature_heating_set": [ 420 | "Temperature Heating Demand", 421 | EcotouchTag.TEMPERATURE_HEATING_SET, 422 | DEVICE_CLASS_TEMPERATURE, 423 | TEMP_CELSIUS, 424 | "mdi:thermometer", 425 | False, 426 | None, 427 | None, 428 | ], 429 | "temperature_heating_set2": [ 430 | "Temperature Heating Temperatur(set2)", 431 | EcotouchTag.TEMPERATURE_HEATING_SET2, 432 | DEVICE_CLASS_TEMPERATURE, 433 | TEMP_CELSIUS, 434 | "mdi:thermometer", 435 | False, 436 | None, 437 | None, 438 | ], 439 | "temperature_cooling_set": [ 440 | "Temperature Cooling Demand", 441 | EcotouchTag.TEMPERATURE_COOLING_SET, 442 | DEVICE_CLASS_TEMPERATURE, 443 | TEMP_CELSIUS, 444 | "mdi:thermometer", 445 | False, 446 | None, 447 | None, 448 | ], 449 | "temperature_cooling_set2": [ 450 | "Temperature Cooling Temperatur(set2)", 451 | EcotouchTag.TEMPERATURE_COOLING_SET2, 452 | DEVICE_CLASS_TEMPERATURE, 453 | TEMP_CELSIUS, 454 | "mdi:thermometer", 455 | False, 456 | None, 457 | None, 458 | ], 459 | "compressor_power": [ 460 | "Compressor Power", 461 | EcotouchTag.COMPRESSOR_POWER, 462 | DEVICE_CLASS_ENERGY, 463 | None, 464 | "mdi:thermometer", 465 | False, 466 | None, 467 | None, 468 | ], 469 | "temperature_norm_outdoor": [ 470 | "Temperature norm outdoor", 471 | EcotouchTag.NVI_NORM_AUSSEN, 472 | DEVICE_CLASS_TEMPERATURE, 473 | TEMP_CELSIUS, 474 | "mdi:thermometer", 475 | False, 476 | None, 477 | None, 478 | ], 479 | "temperature_norm_heating_circle": [ 480 | "Temperature norm heating circle", 481 | EcotouchTag.NVI_HEIZKREIS_NORM, 482 | DEVICE_CLASS_TEMPERATURE, 483 | TEMP_CELSIUS, 484 | "mdi:thermometer", 485 | False, 486 | None, 487 | None, 488 | ], 489 | "temperature_heating_limit": [ 490 | "Temperature heating limit", 491 | EcotouchTag.NVI_T_HEIZGRENZE, 492 | DEVICE_CLASS_TEMPERATURE, 493 | TEMP_CELSIUS, 494 | "mdi:thermometer", 495 | False, 496 | None, 497 | None, 498 | ], 499 | "temperature_heating_limit_target": [ 500 | "Temperature heating limit target", 501 | EcotouchTag.NVI_T_HEIZGRENZE_SOLL, 502 | DEVICE_CLASS_TEMPERATURE, 503 | TEMP_CELSIUS, 504 | "mdi:thermometer", 505 | False, 506 | None, 507 | None, 508 | ], 509 | "temperature_max_heating": [ 510 | "Limit for setpoint (Max.)", 511 | EcotouchTag.MAX_VL_TEMP, 512 | DEVICE_CLASS_TEMPERATURE, 513 | TEMP_CELSIUS, 514 | "mdi:thermometer", 515 | False, 516 | None, 517 | None, 518 | ], 519 | "temperature_out_begin": [ 520 | "Temperature enable Cooling", 521 | EcotouchTag.COOL_ENABLE_TEMP, 522 | DEVICE_CLASS_TEMPERATURE, 523 | TEMP_CELSIUS, 524 | "mdi:thermometer", 525 | False, 526 | None, 527 | None, 528 | ], 529 | "temperature_cooling": [ 530 | "Temperature Cooling", 531 | EcotouchTag.NVI_SOLL_KUEHLEN, 532 | DEVICE_CLASS_TEMPERATURE, 533 | TEMP_CELSIUS, 534 | "mdi:thermometer", 535 | False, 536 | None, 537 | None, 538 | ], 539 | "temperature_mixing1": [ 540 | "Temperature Mixing1", 541 | EcotouchTag.TEMPERATURE_MIXING1_CURRENT, 542 | DEVICE_CLASS_TEMPERATURE, 543 | TEMP_CELSIUS, 544 | "mdi:thermometer", 545 | False, 546 | None, 547 | None, 548 | ], 549 | "temperature_mixing2": [ 550 | "Temperature Mixing2", 551 | EcotouchTag.TEMPERATURE_MIXING2_CURRENT, 552 | DEVICE_CLASS_TEMPERATURE, 553 | TEMP_CELSIUS, 554 | "mdi:thermometer", 555 | False, 556 | None, 557 | None, 558 | ], 559 | "temperature_mixing3": [ 560 | "Temperature Mixing3", 561 | EcotouchTag.TEMPERATURE_MIXING3_CURRENT, 562 | DEVICE_CLASS_TEMPERATURE, 563 | TEMP_CELSIUS, 564 | "mdi:thermometer", 565 | False, 566 | None, 567 | None, 568 | ], 569 | "heatpump_cop": [ 570 | "COP", 571 | EcotouchTag.HEATPUMP_COP, 572 | None, 573 | None, 574 | "mdi:thermometer", 575 | False, 576 | None, 577 | None, 578 | ], 579 | "heatpump_cop_year": [ 580 | "COP Year", 581 | EcotouchTag.HEATPUMP_COP_YEAR, 582 | None, 583 | None, 584 | "mdi:thermometer", 585 | False, 586 | None, 587 | None, 588 | ], 589 | "anual_consumption_compressor": [ 590 | "Anual Consumption Compressor", 591 | EcotouchTag.ANUAL_CONSUMPTION_COMPRESSOR, 592 | DEVICE_CLASS_ENERGY, 593 | None, 594 | None, 595 | False, 596 | None, 597 | None, 598 | ], 599 | "anual_consumption_heating": [ 600 | "Anual Consumption Heating", 601 | EcotouchTag.ANUAL_CONSUMPTION_HEATING, 602 | DEVICE_CLASS_ENERGY, 603 | None, 604 | None, 605 | False, 606 | None, 607 | None, 608 | ], 609 | "anual_consumption_water": [ 610 | "Anual Consumption Water", 611 | EcotouchTag.ANUAL_CONSUMPTION_WATER, 612 | DEVICE_CLASS_ENERGY, 613 | None, 614 | None, 615 | False, 616 | None, 617 | None, 618 | ], 619 | } 620 | """ 621 | 622 | TEMPERATURE_WATER_SETPOINT = TagData(["A37"], "°C", writeable=True) 623 | TEMPERATURE_WATER_SETPOINT2 = TagData(["A38"], "°C", writeable=True) 624 | TEMPERATURE_POOL_SETPOINT = TagData(["A40"], "°C", writeable=True) 625 | TEMPERATURE_POOL_SETPOINT2 = TagData(["A41"], "°C", writeable=True) 626 | HYSTERESIS_HEATING = TagData(["A61"], "?") # Hysteresis setpoint 627 | TEMP_SET_0_DEG = TagData(["A97"], "°C") 628 | 629 | ALARM = TagData(["I52"]) 630 | INTERRUPTIONS = TagData(["I53"]) 631 | ADAPT_HEATING = TagData(["I263"], writeable=True) 632 | MANUAL_HEATINGPUMP = TagData(["I1270"]) 633 | MANUAL_SOURCEPUMP = TagData(["I1281"]) 634 | MANUAL_SOLARPUMP1 = TagData(["I1287"]) 635 | MANUAL_SOLARPUMP2 = TagData(["I1289"]) 636 | MANUAL_TANKPUMP = TagData(["I1291"]) 637 | MANUAL_VALVE = TagData(["I1293"]) 638 | MANUAL_POOLVALVE = TagData(["I1295"]) 639 | MANUAL_COOLVALVE = TagData(["I1297"]) 640 | MANUAL_4WAYVALVE = TagData(["I1299"]) 641 | MANUAL_MULTIEXT = TagData(["I1319"]) """ 642 | 643 | 644 | # async def async_setup_entry(hass: HomeAssistantType, entry: ConfigType, async_add_entities) -> None: 645 | async def async_setup_entry( 646 | hass: HomeAssistantType, entry: ConfigType, async_add_devices 647 | ) -> None: 648 | """Set up the Waterkotte sensor platform.""" 649 | 650 | _LOGGER.debug("Sensor async_setup_entry") 651 | coordinator = hass.data[DOMAIN][entry.entry_id] 652 | async_add_devices( 653 | [ 654 | WaterkotteHeatpumpSensor(entry, coordinator, sensor_type) 655 | for sensor_type in SENSOR_TYPES 656 | ] 657 | ) 658 | 659 | 660 | class WaterkotteHeatpumpSensor(SensorEntity, WaterkotteHeatpumpEntity): 661 | """waterkotte_heatpump Sensor class.""" 662 | 663 | def __init__( 664 | self, entry, hass_data, sensor_type 665 | ): # pylint: disable=unused-argument 666 | """Initialize the sensor.""" 667 | # super().__init__(self, hass_data) 668 | self._coordinator = hass_data 669 | 670 | self._type = sensor_type 671 | self._name = f"{SENSOR_TYPES[self._type][0]}" 672 | self._unique_id = self._type 673 | self._entry_data = entry.data 674 | self._device_id = entry.entry_id 675 | hass_data.alltags.update({self._unique_id: SENSOR_TYPES[self._type][1]}) 676 | super().__init__(hass_data, entry) 677 | 678 | @property 679 | def name(self): 680 | """Return the name of the sensor.""" 681 | return self._name 682 | 683 | @property 684 | def tag(self): 685 | """Return a unique ID to use for this entity.""" 686 | return SENSOR_TYPES[self._type][1] 687 | 688 | @property 689 | def state(self): 690 | """Return the state of the sensor.""" 691 | # result = "" 692 | # print(self._coordinator.data) 693 | try: 694 | sensor = SENSOR_TYPES[self._type] 695 | value = self._coordinator.data[sensor[1]]["value"] 696 | if value is None or value == "": 697 | value = "unknown" 698 | except KeyError: 699 | value = "unknown" 700 | except TypeError: 701 | return "unknown" 702 | if value is True: 703 | value = "on" 704 | elif value is False: 705 | value = "off" 706 | return value 707 | 708 | @property 709 | def icon(self): 710 | """Return the icon of the sensor.""" 711 | return SENSOR_TYPES[self._type][4] 712 | # return ICON 713 | 714 | @property 715 | def device_class(self): 716 | """Return the device class of the sensor.""" 717 | return SENSOR_TYPES[self._type][2] 718 | 719 | @property 720 | def entity_registry_enabled_default(self): 721 | """Return the entity_registry_enabled_default of the sensor.""" 722 | return SENSOR_TYPES[self._type][5] 723 | 724 | @property 725 | def entity_category(self): 726 | """Return the unit of measurement.""" 727 | return SENSOR_TYPES[self._type][7] 728 | 729 | @property 730 | def unique_id(self): 731 | """Return the unique of the sensor.""" 732 | return self._unique_id 733 | 734 | @property 735 | def unit_of_measurement(self): 736 | """Return the unit of measurement.""" 737 | return SENSOR_TYPES[self._type][3] 738 | 739 | async def async_update(self): 740 | """Schedule a custom update via the common entity update service.""" 741 | await self._coordinator.async_request_refresh() 742 | 743 | @property 744 | def should_poll(self) -> bool: 745 | """Entities do not individually poll.""" 746 | return False 747 | -------------------------------------------------------------------------------- /custom_components/waterkotte_heatpump/service.py: -------------------------------------------------------------------------------- 1 | """ Service to setup time for holiday mode """ 2 | # from .const import DOMAIN 3 | import datetime 4 | #from re import M 5 | from pywaterkotte.ecotouch import EcotouchTag 6 | #import voluptuous as vol 7 | #from typing import Sequence 8 | #from homeassistant.util.json import JsonObjectType 9 | from homeassistant.core import HomeAssistant, ServiceCall, ServiceResponse, SupportsResponse 10 | 11 | class WaterkotteHeatpumpService(): 12 | """waterkotte_heatpump switch class.""" 13 | 14 | # GET_ENERGY_BALANCE_SERVICE_NAME = "get_energy_balance" 15 | # GET_ENERGY_BALANCE_SCHEMA = vol.Schema({ 16 | # vol.Required("year"): int, 17 | # vol.Required("cop"): float, 18 | # vol.Required("compressor"): float, 19 | # vol.Required("sourcepump"): float, 20 | # vol.Required("externalheater"): float, 21 | # vol.Required("heating"): float, 22 | # vol.Required("warmwater"): float, 23 | # vol.Required("pool"): float, 24 | # }) 25 | 26 | 27 | def __init__(self, hass, config, coordinator): # pylint: disable=unused-argument 28 | """Initialize the sensor.""" 29 | self._hass = hass 30 | self._config = config 31 | self._coordinator = coordinator 32 | 33 | async def set_holiday(self, call): 34 | """Handle the service call.""" 35 | # name = call.data.get(ATTR_NAME, DEFAULT_NAME) 36 | start = call.data.get('start', None) 37 | end = call.data.get('end', None) 38 | if start is not None and end is not None: 39 | start = datetime.datetime.strptime(start, '%Y-%m-%d %H:%M:%S') 40 | end = datetime.datetime.strptime(end, '%Y-%m-%d %H:%M:%S') 41 | print(f"Start: {start} End: {end}") 42 | try: 43 | # print(option) 44 | # await self._coordinator.api.async_write_value(SENSOR_TYPES[self._type][1], option) 45 | await self._coordinator.async_write_tag(EcotouchTag.HOLIDAY_START_TIME, start) 46 | await self._coordinator.async_write_tag(EcotouchTag.HOLIDAY_END_TIME, end) 47 | # sensor = SENSOR_TYPES[self._type] 48 | # return self._coordinator.data[sensor[1]]["value"] 49 | except ValueError: 50 | return "unavailable" 51 | # self._hass.states.set("waterkotte.set_holiday", name) 52 | 53 | async def get_energy_balance(self, call: ServiceCall) -> ServiceResponse: 54 | """Handle the service call.""" 55 | 56 | try: 57 | tags=[EcotouchTag.ANUAL_CONSUMPTION_COMPRESSOR, 58 | EcotouchTag.ANUAL_CONSUMPTION_SOURCEPUMP, 59 | EcotouchTag.ANUAL_CONSUMPTION_EXTERNALHEATER, 60 | EcotouchTag.ANUAL_CONSUMPTION_HEATING, 61 | EcotouchTag.ANUAL_CONSUMPTION_WATER, 62 | EcotouchTag.ANUAL_CONSUMPTION_POOL, 63 | EcotouchTag.HEATPUMP_COP, 64 | EcotouchTag.HEATPUMP_COP_YEAR] 65 | res = await self._coordinator.async_read_values(tags) 66 | 67 | except ValueError: 68 | return "unavailable" 69 | ret= { 70 | "year":res.get(EcotouchTag.HEATPUMP_COP_YEAR,{"value":"unknown"})["value"], 71 | "cop":res.get(EcotouchTag.HEATPUMP_COP,{"value":"unknown"})["value"], 72 | "compressor":res.get(EcotouchTag.ANUAL_CONSUMPTION_COMPRESSOR,{"value":"unknown"})["value"], 73 | "sourcepump":res.get(EcotouchTag.ANUAL_CONSUMPTION_SOURCEPUMP,{"value":"unknown"})["value"], 74 | "externalheater":res.get(EcotouchTag.ANUAL_CONSUMPTION_EXTERNALHEATER,{"value":"unknown"})["value"], 75 | "heating":res.get(EcotouchTag.ANUAL_CONSUMPTION_HEATING,{"value":"unknown"})["value"], 76 | "warmwater":res.get(EcotouchTag.ANUAL_CONSUMPTION_WATER,{"value":"unknown"})["value"], 77 | "pool":res.get(EcotouchTag.ANUAL_CONSUMPTION_POOL,{"value":"unknown"})["value"]} 78 | return ret 79 | 80 | async def get_energy_balance_monthly(self, call: ServiceCall) -> ServiceResponse: 81 | try: 82 | for x in range(2): 83 | tags=[EcotouchTag.CONSUMPTION_COMPRESSOR1, 84 | EcotouchTag.CONSUMPTION_COMPRESSOR2, 85 | EcotouchTag.CONSUMPTION_COMPRESSOR3, 86 | EcotouchTag.CONSUMPTION_COMPRESSOR4, 87 | EcotouchTag.CONSUMPTION_COMPRESSOR5, 88 | EcotouchTag.CONSUMPTION_COMPRESSOR6, 89 | EcotouchTag.CONSUMPTION_COMPRESSOR7, 90 | EcotouchTag.CONSUMPTION_COMPRESSOR8, 91 | EcotouchTag.CONSUMPTION_COMPRESSOR9, 92 | EcotouchTag.CONSUMPTION_COMPRESSOR10, 93 | EcotouchTag.CONSUMPTION_COMPRESSOR11, 94 | EcotouchTag.CONSUMPTION_COMPRESSOR12] 95 | resCompressor = await self._coordinator.async_read_values(tags) 96 | found=False 97 | for value in resCompressor: 98 | if resCompressor.get(value,{"value":"unknown"})["value"] == "unknown": 99 | found=True 100 | if len(resCompressor)==12 and not found: 101 | break 102 | for x in range(2): 103 | tags=[EcotouchTag.CONSUMPTION_SOURCEPUMP1, 104 | EcotouchTag.CONSUMPTION_SOURCEPUMP2, 105 | EcotouchTag.CONSUMPTION_SOURCEPUMP3, 106 | EcotouchTag.CONSUMPTION_SOURCEPUMP4, 107 | EcotouchTag.CONSUMPTION_SOURCEPUMP5, 108 | EcotouchTag.CONSUMPTION_SOURCEPUMP6, 109 | EcotouchTag.CONSUMPTION_SOURCEPUMP7, 110 | EcotouchTag.CONSUMPTION_SOURCEPUMP8, 111 | EcotouchTag.CONSUMPTION_SOURCEPUMP9, 112 | EcotouchTag.CONSUMPTION_SOURCEPUMP10, 113 | EcotouchTag.CONSUMPTION_SOURCEPUMP11, 114 | EcotouchTag.CONSUMPTION_SOURCEPUMP12] 115 | resSourcePump = await self._coordinator.async_read_values(tags) 116 | found=False 117 | for value in resSourcePump: 118 | if resSourcePump.get(value,{"value":"unknown"})["value"] == "unknown": 119 | found=True 120 | if len(resSourcePump)==12 and not found: 121 | break 122 | for x in range(2): 123 | tags=[EcotouchTag.CONSUMPTION_EXTERNALHEATER1, 124 | EcotouchTag.CONSUMPTION_EXTERNALHEATER2, 125 | EcotouchTag.CONSUMPTION_EXTERNALHEATER3, 126 | EcotouchTag.CONSUMPTION_EXTERNALHEATER4, 127 | EcotouchTag.CONSUMPTION_EXTERNALHEATER5, 128 | EcotouchTag.CONSUMPTION_EXTERNALHEATER6, 129 | EcotouchTag.CONSUMPTION_EXTERNALHEATER7, 130 | EcotouchTag.CONSUMPTION_EXTERNALHEATER8, 131 | EcotouchTag.CONSUMPTION_EXTERNALHEATER9, 132 | EcotouchTag.CONSUMPTION_EXTERNALHEATER10, 133 | EcotouchTag.CONSUMPTION_EXTERNALHEATER11, 134 | EcotouchTag.CONSUMPTION_EXTERNALHEATER12] 135 | resExternalHeater = await self._coordinator.async_read_values(tags) 136 | found=False 137 | for value in resExternalHeater: 138 | if resExternalHeater.get(value,{"value":"unknown"})["value"] == "unknown": 139 | found=True 140 | if len(resExternalHeater)==12 and not found: 141 | break 142 | for x in range(2): 143 | tags=[EcotouchTag.CONSUMPTION_HEATING1, 144 | EcotouchTag.CONSUMPTION_HEATING2, 145 | EcotouchTag.CONSUMPTION_HEATING3, 146 | EcotouchTag.CONSUMPTION_HEATING4, 147 | EcotouchTag.CONSUMPTION_HEATING5, 148 | EcotouchTag.CONSUMPTION_HEATING6, 149 | EcotouchTag.CONSUMPTION_HEATING7, 150 | EcotouchTag.CONSUMPTION_HEATING8, 151 | EcotouchTag.CONSUMPTION_HEATING9, 152 | EcotouchTag.CONSUMPTION_HEATING10, 153 | EcotouchTag.CONSUMPTION_HEATING11, 154 | EcotouchTag.CONSUMPTION_HEATING12] 155 | resHeater = await self._coordinator.async_read_values(tags) 156 | found=False 157 | for value in resHeater: 158 | if resHeater.get(value,{"value":"unknown"})["value"] == "unknown": 159 | found=True 160 | if len(resHeater)==12 and not found: 161 | break 162 | for x in range(2): 163 | tags=[EcotouchTag.CONSUMPTION_WARMWATER1, 164 | EcotouchTag.CONSUMPTION_WARMWATER2, 165 | EcotouchTag.CONSUMPTION_WARMWATER3, 166 | EcotouchTag.CONSUMPTION_WARMWATER4, 167 | EcotouchTag.CONSUMPTION_WARMWATER5, 168 | EcotouchTag.CONSUMPTION_WARMWATER6, 169 | EcotouchTag.CONSUMPTION_WARMWATER7, 170 | EcotouchTag.CONSUMPTION_WARMWATER8, 171 | EcotouchTag.CONSUMPTION_WARMWATER9, 172 | EcotouchTag.CONSUMPTION_WARMWATER10, 173 | EcotouchTag.CONSUMPTION_WARMWATER11, 174 | EcotouchTag.CONSUMPTION_WARMWATER12] 175 | resWarmWater = await self._coordinator.async_read_values(tags) 176 | found=False 177 | for value in resWarmWater: 178 | if resWarmWater.get(value,{"value":"unknown"})["value"] == "unknown": 179 | found=True 180 | if len(resWarmWater)==12 and not found: 181 | break 182 | for x in range(2): 183 | tags=[EcotouchTag.CONSUMPTION_POOL1, 184 | EcotouchTag.CONSUMPTION_POOL2, 185 | EcotouchTag.CONSUMPTION_POOL3, 186 | EcotouchTag.CONSUMPTION_POOL4, 187 | EcotouchTag.CONSUMPTION_POOL5, 188 | EcotouchTag.CONSUMPTION_POOL6, 189 | EcotouchTag.CONSUMPTION_POOL7, 190 | EcotouchTag.CONSUMPTION_POOL8, 191 | EcotouchTag.CONSUMPTION_POOL9, 192 | EcotouchTag.CONSUMPTION_POOL10, 193 | EcotouchTag.CONSUMPTION_POOL11, 194 | EcotouchTag.CONSUMPTION_POOL12] 195 | resPool = await self._coordinator.async_read_values(tags) 196 | found=False 197 | for value in resPool: 198 | if resPool.get(value,{"value":"unknown"})["value"] == "unknown": 199 | found=True 200 | if len(resPool)==12 and not found: 201 | break 202 | for x in range(2): 203 | tags=[EcotouchTag.HEATPUMP_COP_MONTH1, 204 | EcotouchTag.HEATPUMP_COP_MONTH2, 205 | EcotouchTag.HEATPUMP_COP_MONTH3, 206 | EcotouchTag.HEATPUMP_COP_MONTH4, 207 | EcotouchTag.HEATPUMP_COP_MONTH5, 208 | EcotouchTag.HEATPUMP_COP_MONTH6, 209 | EcotouchTag.HEATPUMP_COP_MONTH7, 210 | EcotouchTag.HEATPUMP_COP_MONTH8, 211 | EcotouchTag.HEATPUMP_COP_MONTH9, 212 | EcotouchTag.HEATPUMP_COP_MONTH10, 213 | EcotouchTag.HEATPUMP_COP_MONTH11, 214 | EcotouchTag.HEATPUMP_COP_MONTH12] 215 | resHeatpumpCopMonth = await self._coordinator.async_read_values(tags) 216 | found=False 217 | for value in resHeatpumpCopMonth: 218 | if resHeatpumpCopMonth.get(value,{"value":"unknown"})["value"] == "unknown": 219 | found=True 220 | if len(resHeatpumpCopMonth)==12 and not found: 221 | break 222 | for x in range(2): 223 | tags=[EcotouchTag.DATE_MONTH, 224 | EcotouchTag.DATE_YEAR, 225 | EcotouchTag.HEATPUMP_COP, 226 | EcotouchTag.HEATPUMP_COP_YEAR] 227 | resDate = await self._coordinator.async_read_values(tags) 228 | found=False 229 | for value in resDate: 230 | if resDate.get(value,{"value":"unknown"})["value"] == "unknown": 231 | found=True 232 | if len(resDate)==4 and not found: 233 | break 234 | except ValueError: 235 | return "unavailable" 236 | ret= { 237 | "cop_year":resDate.get(EcotouchTag.HEATPUMP_COP_YEAR,{"value":"unknown"})["value"], 238 | "cop":resDate.get(EcotouchTag.HEATPUMP_COP,{"value":"unknown"})["value"], 239 | "heatpump_month":resDate.get(EcotouchTag.DATE_MONTH,{"value":"unknown"})["value"], 240 | "heatpump_year":resDate.get(EcotouchTag.DATE_YEAR,{"value":"unknown"})["value"], 241 | "month1":{ 242 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH1,{"value":"unknown"})["value"], 243 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR1,{"value":"unknown"})["value"], 244 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP1,{"value":"unknown"})["value"], 245 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER1,{"value":"unknown"})["value"], 246 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING1,{"value":"unknown"})["value"], 247 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER1,{"value":"unknown"})["value"], 248 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL1,{"value":"unknown"})["value"] 249 | }, 250 | "month2":{ 251 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH2,{"value":"unknown"})["value"], 252 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR2,{"value":"unknown"})["value"], 253 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP2,{"value":"unknown"})["value"], 254 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER2,{"value":"unknown"})["value"], 255 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING2,{"value":"unknown"})["value"], 256 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER2,{"value":"unknown"})["value"], 257 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL2,{"value":"unknown"})["value"] 258 | }, 259 | "month3":{ 260 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH3,{"value":"unknown"})["value"], 261 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR3,{"value":"unknown"})["value"], 262 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP3,{"value":"unknown"})["value"], 263 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER3,{"value":"unknown"})["value"], 264 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING3,{"value":"unknown"})["value"], 265 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER3,{"value":"unknown"})["value"], 266 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL3,{"value":"unknown"})["value"] 267 | }, 268 | "month4":{ 269 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH4,{"value":"unknown"})["value"], 270 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR4,{"value":"unknown"})["value"], 271 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP4,{"value":"unknown"})["value"], 272 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER4,{"value":"unknown"})["value"], 273 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING4,{"value":"unknown"})["value"], 274 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER4,{"value":"unknown"})["value"], 275 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL4,{"value":"unknown"})["value"] 276 | }, 277 | "month5":{ 278 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH5,{"value":"unknown"})["value"], 279 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR5,{"value":"unknown"})["value"], 280 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP5,{"value":"unknown"})["value"], 281 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER5,{"value":"unknown"})["value"], 282 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING5,{"value":"unknown"})["value"], 283 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER5,{"value":"unknown"})["value"], 284 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL5,{"value":"unknown"})["value"] 285 | }, 286 | "month6":{ 287 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH6,{"value":"unknown"})["value"], 288 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR6,{"value":"unknown"})["value"], 289 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP6,{"value":"unknown"})["value"], 290 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER6,{"value":"unknown"})["value"], 291 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING6,{"value":"unknown"})["value"], 292 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER6,{"value":"unknown"})["value"], 293 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL6,{"value":"unknown"})["value"] 294 | }, 295 | "month7":{ 296 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH7,{"value":"unknown"})["value"], 297 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR7,{"value":"unknown"})["value"], 298 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP7,{"value":"unknown"})["value"], 299 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER7,{"value":"unknown"})["value"], 300 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING7,{"value":"unknown"})["value"], 301 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER7,{"value":"unknown"})["value"], 302 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL7,{"value":"unknown"})["value"] 303 | }, 304 | "month8":{ 305 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH8,{"value":"unknown"})["value"], 306 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR8,{"value":"unknown"})["value"], 307 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP8,{"value":"unknown"})["value"], 308 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER8,{"value":"unknown"})["value"], 309 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING8,{"value":"unknown"})["value"], 310 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER8,{"value":"unknown"})["value"], 311 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL8,{"value":"unknown"})["value"] 312 | }, 313 | "month9":{ 314 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH9,{"value":"unknown"})["value"], 315 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR9,{"value":"unknown"})["value"], 316 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP9,{"value":"unknown"})["value"], 317 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER9,{"value":"unknown"})["value"], 318 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING9,{"value":"unknown"})["value"], 319 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER9,{"value":"unknown"})["value"], 320 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL9,{"value":"unknown"})["value"] 321 | }, 322 | "month10":{ 323 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH10,{"value":"unknown"})["value"], 324 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR10,{"value":"unknown"})["value"], 325 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP10,{"value":"unknown"})["value"], 326 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER10,{"value":"unknown"})["value"], 327 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING10,{"value":"unknown"})["value"], 328 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER10,{"value":"unknown"})["value"], 329 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL10,{"value":"unknown"})["value"] 330 | }, 331 | "month11":{ 332 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH11,{"value":"unknown"})["value"], 333 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR11,{"value":"unknown"})["value"], 334 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP11,{"value":"unknown"})["value"], 335 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER11,{"value":"unknown"})["value"], 336 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING11,{"value":"unknown"})["value"], 337 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER11,{"value":"unknown"})["value"], 338 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL11,{"value":"unknown"})["value"] 339 | }, 340 | "month12":{ 341 | "cop":resHeatpumpCopMonth.get(EcotouchTag.HEATPUMP_COP_MONTH12,{"value":"unknown"})["value"], 342 | "compressor":resCompressor.get(EcotouchTag.CONSUMPTION_COMPRESSOR12,{"value":"unknown"})["value"], 343 | "sourcepump":resSourcePump.get(EcotouchTag.CONSUMPTION_SOURCEPUMP12,{"value":"unknown"})["value"], 344 | "externalheater":resExternalHeater.get(EcotouchTag.CONSUMPTION_EXTERNALHEATER12,{"value":"unknown"})["value"], 345 | "heating":resHeater.get(EcotouchTag.CONSUMPTION_HEATING12,{"value":"unknown"})["value"], 346 | "warmwater":resWarmWater.get(EcotouchTag.CONSUMPTION_WARMWATER12,{"value":"unknown"})["value"], 347 | "pool":resPool.get(EcotouchTag.CONSUMPTION_POOL12,{"value":"unknown"})["value"] 348 | } 349 | } 350 | return ret --------------------------------------------------------------------------------