├── .github └── workflows │ ├── release.yaml │ └── toc.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── RECIPES.md ├── custom_components └── gardena_smart_system │ ├── __init__.py │ ├── binary_sensor.py │ ├── button.py │ ├── config_flow.py │ ├── const.py │ ├── lawn_mower.py │ ├── manifest.json │ ├── sensor.py │ ├── services.yaml │ ├── strings.json │ ├── switch.py │ └── translations │ ├── de.json │ ├── en.json │ ├── fi.json │ ├── fr.json │ ├── nb.json │ ├── nl.json │ ├── sk.json │ ├── sv.json │ ├── vacuum.da.json │ ├── vacuum.de.json │ ├── vacuum.en.json │ ├── vacuum.fi.json │ ├── vacuum.nl.json │ ├── vacuum.sk.json │ └── vacuum.sv.json ├── hacs.json └── info.md /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - '*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create Release 8 | 9 | jobs: 10 | build: 11 | name: Create Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | - name: Create Release 17 | id: create_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: Release ${{ github.ref }} 24 | body: | 25 | Release ${{ github.ref }} 26 | draft: false 27 | prerelease: true -------------------------------------------------------------------------------- /.github/workflows/toc.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: TOC Generator 3 | jobs: 4 | generateTOC: 5 | name: TOC Generator 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: technote-space/toc-generator@v2 9 | with: 10 | TARGET_PATHS: 'README.md,info.md' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Packages 2 | *.egg 3 | *.egg-info 4 | eggs 5 | .eggs 6 | 7 | # GITHUB Proposed Python stuff: 8 | *.py[cod] 9 | 10 | # emacs auto backups 11 | *~ 12 | *# 13 | *.orig 14 | .idea 15 | .venv/ 16 | .venv-test/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) 2 | [![hass-gardena-smart-system](https://img.shields.io/github/release/py-smart-gardena/hass-gardena-smart-system.svg?1)](https://github.com/py-smart-gardena/hass-gardena-smart-system) 3 | 4 | # Home Assistant integration for Gardena Smart System 5 | 6 | Custom component to support Gardena Smart System devices. 7 | 8 | 9 | 10 | **Table of Contents** 11 | 12 | - [About](#about) 13 | - [Installation](#installation) 14 | - [Installation through HACS](#installation-through-hacs) 15 | - [Manual installation](#manual-installation) 16 | - [Configuration](#configuration) 17 | - [Home Assistant](#home-assistant) 18 | - [Gardena Application Key / Client ID and Application secret / client secret](#gardena-application-key--client-id-and-application-secret--client-secret) 19 | - [Supported devices](#supported-devices) 20 | - [Services](#services) 21 | - [Smart Irrigation Control services](#smart-irrigation-control-services) 22 | - [Smart Mower services](#smart-mower-services) 23 | - [Smart System general](#smart-system-general) 24 | - [Smart Power Socket services](#smart-power-socket-services) 25 | - [Smart Sensor services](#smart-sensor-services) 26 | - [Smart Water Control services](#smart-water-control-services) 27 | - [Smart Irigation Control services](#smart-irigation-control-services) 28 | - [Recipes](#recipes) 29 | - [Development](#development) 30 | - [Debugging](#debugging) 31 | - [TODO](#todo) 32 | 33 | 34 | 35 | > :warning: **Starting from version 1.0.0b5: You might probably have to uninstall and reinstall the integration as credentials requirements and method has changed. THERE IS A BREAKING CHANGE IN THE CONFIGURATION DUE TO AN UPDATE ON THE GARDENA API** 36 | 37 | 38 | ## About 39 | 40 | This component is originally based on 41 | https://github.com/grm/home-assistant/tree/feature/smart_gardena and 42 | https://github.com/grm/py-smart-gardena 43 | 44 | The integration / component has been changed quite a lot, mainly to 45 | add support for config flow setup and Home Assistant devices. It has 46 | also been cleaned up and some bugs have been fixed. Gardena devices 47 | are now represented as Home Assistant devices, which have battery 48 | level sensors where applicable. 49 | 50 | **This project needs your support.** 51 | Gardena equipments are expensive, and I need to buy them in order to add support. 52 | If you find this library useful and want to help me support more devices (or if you 53 | just want to reward me for my spent time), you are very welcome ! 54 | Your help is very much appreciated. 55 | 56 | Here are the links if you want to show your support : 57 | PayPal donate button 58 | 59 | ## Installation 60 | 61 | Requires Home Assistant 0.115.0 or newer. 62 | 63 | ### Installation through HACS 64 | 65 | If you have not yet installed HACS, go get it at https://hacs.xyz/ and walk through the installation and configuration. 66 | 67 | Then find the Gardena Smart System integration in HACS and install it. 68 | 69 | Restart Home Assistant! 70 | 71 | Install the new integration through *Configuration -> Integrations* in HA (see below). 72 | 73 | 74 | ### Manual installation 75 | 76 | Copy the sub-path `/hass-gardena-smart-system/custom_components/gardena_smart_system` of this repo into the path `/config/custom_components/gardena_smart_system` of your HA installation. 77 | 78 | Alternatively use the following commands within an SSH shell into your HA system. 79 | Do NOT try to execute these commands directly your PC on a mounted HA file system. The resulting symlink would be broken for the HA file system. 80 | ``` 81 | cd /config 82 | git clone https://github.com/py-smart-gardena/hass-gardena-smart-system.git 83 | 84 | # if folder custom_components does not yet exist: 85 | mkdir custom_components 86 | 87 | cd custom_components 88 | ln -s ../hass-gardena-smart-system/custom_components/gardena_smart_system 89 | ``` 90 | 91 | ## Configuration 92 | 93 | 94 | ### Home Assistant 95 | 96 | Setup under Integrations in Home Assistant, search for "Gardena Smart 97 | System". You need to enter your application key / client ID and your applications secret / client secret. See below for how to get your Gardena application key and secret. 98 | 99 | After setting up the integration, you can adjust some options on the 100 | integration panel for it. 101 | 102 | Even though this integration can be installed and configured via the 103 | Home Assistant GUI (uses config flow), you might have to restart Home 104 | Assistant to get it working. 105 | 106 | 107 | ### Gardena Application Key / Client ID and Application secret / client secret 108 | 109 | In order to use this integration you must get a client ID / 110 | Application Key from Gardena/Husqvarna. 111 | 112 | 1. Go to https://developer.husqvarnagroup.cloud/ 113 | 114 | 2. Create an account if needed, otherwise sign in with your Gardena 115 | account. 116 | 117 | 3. After signing in you will be automatically redirected to "Your 118 | applications". (Otherwise go to: https://developer.husqvarnagroup.cloud/applications) 119 | 120 | 4. Create an new application, name it for example "My Home Assistant" 121 | (doesn't matter), leave the other fields empty. 122 | 123 | 5. Click on "+Connect new API" and connect the Authentication API and 124 | the GARDENA smart system API. 125 | 126 | 6. Copy your Application Key and Application secret, this is what you need when you add the integration in Home Assistant. 127 | 128 | 129 | ## Supported devices 130 | 131 | The following devices are supported : 132 | 133 | * Gardena Smart Irrigation Control (as switch) 134 | * Gardena Smart Mower (as lawn_mower) 135 | * Gardena Smart Sensor (as sensor) 136 | * Gardena Smart Water Control (as switch) 137 | * Gardena Smart Power Socket (as switch) 138 | 139 | ## Services 140 | 141 | ### Smart Irrigation Control services 142 | 143 | > [TODO: document services] 144 | 145 | ### Smart Mower services 146 | 147 | `lawn_mower.start_mowing` 148 | Start the mower using the Gardena API command START_DONT_OVERRIDE. 149 | The mower resumes the schedule. 150 | 151 | `lawn_mower.pause` 152 | Stop the mower using the Gardena API command PARK_UNTIL_FURTHER_NOTICE. 153 | The mower cancels the current operation, returns to charging station and ignores schedule. 154 | 155 | `lawn_mower.dock` 156 | Stop the mower using Gardena API command PARK_UNTIL_NEXT_TASK. 157 | The mower cancels the current operation and returns to charging station. It will reactivate with the next schedule. 158 | 159 | `gardena_smart_system.start_override` 160 | Start the mower using the Gardena API command START_SECONDS_TO_OVERRIDE. 161 | The mower starts immediately for the duration parameter of the action. 162 | 163 | ### Smart System general 164 | 165 | `binary_sensor.gardena_smart_system_connection` 166 | Checks if service is connected or disconnected 167 | 168 | ### Smart Power Socket services 169 | 170 | > [TODO: document services] 171 | 172 | ### Smart Sensor services 173 | 174 | `sensor.sensor_light_intensity` 175 | Read the light intensity of a smart sensor. Only V1 of the Smart Sensor supports this. 176 | 177 | `sensor.sensor_soil_humidity` 178 | Read the soil humidity of a smart sensor. This can help automate your Water control / Irrigation Controller. 179 | 180 | `sensor.sensor_soil_temperature` 181 | Read the soil tempature. 182 | 183 | `sensor.sensor_ambient_temperature` 184 | Read the ambient tempature of a smart sensor. 185 | 186 | `sensor.sensor_battery_level` 187 | Read the battery level of a smart sensor. 188 | 189 | ### Smart Water Control services 190 | 191 | > [TODO: document services] 192 | 193 | ### Smart Irigation Control services 194 | 195 | `switch.irrigation_control_valve_X` 196 | Open or close a valve to start watering . Irrigation control can have up to 6 valves and inherits the original name of each valve from the Gardena app. If a valve is not connected, the service for the specific valve is unavailable. 197 | 198 | 199 | 200 | ## Recipes 201 | 202 | Some recips were made by the community. 203 | You can find them [here](RECIPES.md). 204 | 205 | ## Development 206 | 207 | ### Debugging 208 | 209 | To enable debug logging for this integration and related libraries you 210 | can control this in your Home Assistant `configuration.yaml` 211 | file. Example: 212 | 213 | ``` 214 | logger: 215 | default: info 216 | logs: 217 | custom_components.gardena_smart_system: debug 218 | custom_components.gardena_smart_system.mower : debug 219 | custom_components.gardena_smart_system.sensor : debug 220 | custom_components.gardena_smart_system.switch : debug 221 | custom_components.gardena_smart_system.config_flow : debug 222 | 223 | gardena: debug 224 | gardena.smart_system: debug 225 | websocket: debug 226 | ``` 227 | 228 | After a restart detailed log entries will appear in `/config/home-assistant.log`. 229 | 230 | ### TODO 231 | 232 | * Do we need support for more than one location? Should we make it 233 | possible to configure it? 234 | -------------------------------------------------------------------------------- /RECIPES.md: -------------------------------------------------------------------------------- 1 | ## TOC 2 | 3 | - [Watering the lawn based on the current soil moisture and time of day. With the aim that the lawn is watered sufficiently but is not too wet.](##watering-the-lawn-based-on-the-current-soil-moisture-and-time-of-day-with-the-aim-that-the-lawn-is-watered-sufficiently-but-is-not-too-wet) 4 | - [Notification over HomeAssistant Companion App or as Telegram Messenger message or over Amazon Alexa with the help of Alexa Media Player Integration](#notification-over-homeassistant-companion-app-or-as-telegram-messenger-message-or-over-amazon-alexa-with-the-help-of-alexa-media-player-integration) 5 | - [Use a NFC tag to start and stop mowing](#use-a-nfc-tag-to-start-and-stop-mowing) 6 | 7 | ## Watering the lawn based on the current soil moisture and time of day. With the aim that the lawn is watered sufficiently but is not too wet. 8 | 9 | ### Why not with the Gardena App: 10 | The problem of Gardena automation for lawn irrigation is that this ... 11 | 12 | 1. not flexible enough for soil moisture 13 | 2. not dynamic enough 14 | 3. Lawn gets too wet 15 | 4. Water consumption is too high 16 | 17 | ### Requirements 18 | - Installed [hass-gardena-smart-system](https://github.com/py-smart-gardena/hass-gardena-smart-system/) integration 19 | - Timer with the need of long-time availability (with HA restart in the between) 20 | ->https://community.home-assistant.io/t/how-to-make-active-timers-survive-a-restart/146248 21 | - [Gardena smart control](https://www.gardena.com/de/produkte/bewasserung/bewasserungssteuerung/smart-water-control/967045101/) 22 | - [Gardena Smart Sensor](https://www.gardena.com/de/produkte/bewasserung/bewasserungssteuerung/smart-sensor/967044801/) or other sensors for depending values 23 | - [Gardena Pipeline System](https://www.gardena.com/de/produkte/bewasserung/pipelines/) 24 | 25 | ### Automation example: 26 | 27 | ```yaml 28 | - id: lawn_irrigation_garden 29 | alias: lawn irrigation garden 30 | trigger: 31 | platform: time_pattern 32 | minutes: '/15' 33 | seconds: 0 34 | condition: 35 | condition: and 36 | conditions: 37 | - condition: state 38 | entity_id: timer.lawn_irrigation_garden 39 | state: 'idle' 40 | - condition: numeric_state 41 | entity_id: sensor.garden_sensor_light_intensity 42 | below: 10 43 | - condition: numeric_state 44 | entity_id: sensor.plant bed_sensor_light_intensity 45 | below: 10 46 | - condition: numeric_state 47 | entity_id: sensor.garden_sensor_ambient_temperature 48 | above: 5 49 | - condition: numeric_state 50 | entity_id: sensor.garden_sensor_soil_humidity 51 | below: 10 52 | action: 53 | - service: timer.start 54 | entity_id: timer.timer.lawn_irrigation_garden 55 | - service: switch.turn_on 56 | entity_id: switch.garden_water_control 57 | - delay: 0:12:00 58 | - service: switch.turn_off 59 | entity_id: switch.garden_water_control 60 | - delay: 0:60:00 61 | - service: switch.turn_on 62 | entity_id: switch.garden_water_control 63 | - delay: 0:24:00 64 | - service: switch.turn_off 65 | entity_id: switch.garden_water_control 66 | - delay: 0:60:00 67 | - service: switch.turn_on 68 | entity_id: switch.garden_water_control 69 | - delay: 0:24:00 70 | - service: switch.turn_off 71 | ``` 72 | 73 | ## Notification over [HomeAssistant Companion App](https://companion.home-assistant.io/) or as [Telegram Messenger](https://www.home-assistant.io/integrations/telegram/) message or over [Amazon Alexa with the help of Alexa Media Player Integration](https://github.com/custom-components/alexa_media_player) 74 | 75 | ### Why not with the Gardena App: 76 | It is not possible to get Messages from the status of the [smart water](https://www.gardena.com/de/produkte/bewasserung/bewasserungssteuerung/smart-water-control/967045101/) or the [sensor](https://www.gardena.com/de/produkte/bewasserung/bewasserungssteuerung/smart-sensor/967044801/) devices. 77 | 78 | It is not possible to get this notifications over Amazon Alexa from the App 79 | 80 | ### Requirements 81 | 82 | 1. Installed hass-gardena-smart-system integration 83 | 2. [Home Assistant Compagnion iOS or Android App](https://companion.home-assistant.io/) 84 | 3. [Gardena smart control](https://www.gardena.com/de/produkte/bewasserung/bewasserungssteuerung/smart-water-control/967045101/) 85 | 4. [Gardena Smart Sensor](https://www.gardena.com/de/produkte/bewasserung/bewasserungssteuerung/smart-sensor/967044801/) or other sensors for depending values 86 | 5. [Gardena Pipeline System](https://www.gardena.com/de/produkte/bewasserung/pipelines/) 87 | 6. (optional) [Telegram Messenger Integration](https://www.home-assistant.io/integrations/telegram/) 88 | 7. (optional) [Alexa Media Player Integration](https://github.com/custom-components/alexa_media_player) you can find and install this Integration over HACS 89 | 90 | ### Configuration: 91 | only needed for Telegram 92 | ```yaml 93 | - alias: "Notify lawn irrigation garden on" 94 | trigger: 95 | platform: state 96 | entity_id: switch.garden_water_control 97 | to: 'on' 98 | action: 99 | - service: notify.notify 100 | data: 101 | title: "lawn irrigation" 102 | message: "Watering in the garden has started" 103 | - service: notify.alexa_media 104 | data: 105 | data: 106 | type: announce 107 | target: 108 | - media_player.radio_livingroom 109 | message: "Watering in the garden has started. The humidity is currently {{ states.sensor.garden_sensor_humidity.state }}% the temperature is now {{ states.sensor.garden_sensor_temperature.state }}°C" 110 | - service: notify.telegram_[you Telegram channel] 111 | data_template: 112 | title: '*Watering in the garden has started!*' 113 | message: "Watering in the garden has started. The humidity is currently {{ states.sensor.garden_sensor_humidity.state }}% the temperature is now {{ states.sensor.garden_sensor_temperature.state }}°C -> https://[public HA URL]/lovelace/terrasse" 114 | data: 115 | inline_keyboard: 116 | - 'Stop watering:/stopwateringgarden' 117 | - ' Stop for 3 hours:/stopwateringgarden3h, Stop for 24 hours:/stopwateringgarden24h' 118 | 119 | - id: 'telegram_stop_watering_garden' 120 | alias: 'Telegram Stop watering garden' 121 | 122 | trigger: 123 | platform: event 124 | event_type: telegram_callback 125 | event_data: 126 | data: '/stopwateringgarden' 127 | action: 128 | - service: telegram_bot.answer_callback_query 129 | data_template: 130 | callback_query_id: '{{ trigger.event.data.id }}' 131 | message: 'OK, I'll stop watering the garden' 132 | - service: switch.turn_off 133 | entity_id: switch.garden_water_control 134 | - service: notify.telegram_[you Telegram channel] 135 | data_template: 136 | title: '*Watering garden!*' 137 | message: "Watering stopped in the garden https://[public HA URL]/lovelace/terrasse" 138 | 139 | - id: 'telegram_stop_watering_garden_3h' 140 | alias: 'Telegram watering stop 3h' 141 | trigger: 142 | platform: event 143 | event_type: telegram_callback 144 | event_data: 145 | data: '/stopwateringgarden3h' 146 | action: 147 | - service: telegram_bot.answer_callback_query 148 | data_template: 149 | callback_query_id: '{{ trigger.event.data.id }}' 150 | message: 'OK, stop watering for 3 hours' 151 | - service: automation.turn_off 152 | entity_id: automation.lawn_irrigation_garden 153 | - service: notify.telegram_[you Telegram channel] 154 | data_template: 155 | title: '*Watering Garden!*' 156 | message: "Irrigation in the garden interrupted for 3 hours https://[public HA URL]/lovelace/terrasse" 157 | - delay: '03:00:00' 158 | - service: automation.turn_on 159 | entity_id: automation.lawn_irrigation_garden 160 | - service: notify.telegram_[you Telegram channel] 161 | data_template: 162 | title: '*Watering Garden!*' 163 | message: "Automation for irrigation in the garden was restarted after 3 hours https://[public HA URL]/lovelace/terrasse" 164 | 165 | - id: 'telegram_stop_watering_garden_24h' 166 | alias: 'Telegram watering stop 24h' 167 | trigger: 168 | platform: event 169 | event_type: telegram_callback 170 | event_data: 171 | data: '/stopwateringgarden24h' 172 | action: 173 | - service: telegram_bot.answer_callback_query 174 | data_template: 175 | callback_query_id: '{{ trigger.event.data.id }}' 176 | message: 'OK, stop watering for 24 hours' 177 | - service: automation.turn_off 178 | entity_id: automation.lawn_irrigation_garden 179 | - service: notify.telegram_[you Telegram channel] 180 | data_template: 181 | title: '*Watering Garden!*' 182 | message: "OK, stop watering for 24 hours https://[public HA URL]/lovelace/terrasse" 183 | - delay: '24:00:00' 184 | - service: automation.turn_on 185 | entity_id: automation.lawn_irrigation_garden 186 | - service: notify.telegram_[you Telegram channel] 187 | data_template: 188 | title: '*Watering Garden!*' 189 | message: "Automation for irrigation in the garden was restarted after 24 hours https://[public HA URL]/lovelace/terrasse" 190 | 191 | - alias: "Notify watering Garden off" 192 | trigger: 193 | platform: state 194 | entity_id: switch.garden_water_control 195 | to: 'off' 196 | action: 197 | - service: notify.notify 198 | data: 199 | title: "Watering Garden" 200 | message: "Watering in the garden has ended" 201 | - service: notify.alexa_media 202 | data: 203 | data: 204 | type: announce 205 | target: 206 | - media_player.radio_wohnzimmer 207 | message: "Watering in the garden has ended. The humidity is now {{ states.sensor.garden_sensor_humidity.state }}%" 208 | - service: notify.telegram_[you Telegram channel] 209 | data_template: 210 | title: '*Watering in the garden has ended!*' 211 | message: "Watering in the garden has ended. The humidity is now {{ states.sensor.garden_sensor_humidity.state }}% -> https://[public HA URL]/lovelace/terrasse" 212 | ``` 213 | ## Use a NFC tag to start and stop mowing 214 | 215 | Normaly my Gardena Irrigation Control works per automations, but in a part of situations i have to start/stop it manualy (i.e. I will fill a pot with water) in this cases i have before use my Smartphone open the App search for the Watercontroll entity and start/stop this. 216 | 217 | Now with the [NFC tag integration](https://companion.home-assistant.io/docs/integrations/universal-links/) from HomeAssistant thats is more easy than befor, now i’m scan with my Smartphone an tag on my Hose trolley and give the okay that the tag starts the HA App and the water control starts if it's off and stopps if it's on. 218 | 219 | Steps for this. 220 | 221 | 1. Buy an NFC tag like this one [https://www.amazon.de/dp/B06Y1BLLD4?ref=ppx_pop_mob_ap_share](https://www.amazon.de/dp/B06Y1BLLD4?ref=ppx_pop_mob_ap_share) 222 | 223 | 2. Install the [HA Companion App](https://companion.home-assistant.io/) on your Smartphone (if you have't do this before) 224 | 225 | 3. [Write the NFC tag with the HA App](https://companion.home-assistant.io/docs/integrations/universal-links/) 226 | ![28CB9BFE-3F41-4D96-91EA-34D6EEA1A0CF](https://user-images.githubusercontent.com/36472486/93881822-b272dc80-fcdf-11ea-9d6a-8d615b4b58d2.jpeg) 227 | 228 | ![1F0AF2E9-4E1B-4634-BB39-9C9A173125BD](https://user-images.githubusercontent.com/36472486/93881365-19dc5c80-fcdf-11ea-9f7c-51e51d0533c1.jpeg) 229 | ![6BA84B75-05BE-4730-8C35-BF274F7E2A82](https://user-images.githubusercontent.com/36472486/93881368-19dc5c80-fcdf-11ea-8264-645fa00ebccc.jpeg) 230 | ![217C9CD3-4F58-48DD-A7FB-EB1211C11F29](https://user-images.githubusercontent.com/36472486/93881369-1a74f300-fcdf-11ea-901c-d9f61b576185.png) 231 | ![F53B6C62-1910-4871-A54F-FDA875E09B78](https://user-images.githubusercontent.com/36472486/93881370-1a74f300-fcdf-11ea-86e7-fa65dfaadbd0.png) 232 | ![0E495C12-8DF9-4B79-8681-D9842C73815C](https://user-images.githubusercontent.com/36472486/93881371-1b0d8980-fcdf-11ea-8418-28134a1e9850.jpeg) 233 | 234 | 4. Go to the [NFC tag configuration in HA](https://www.home-assistant.io/blog/2020/09/15/home-assistant-tags/) and give your NFC tag an readable name and create an Automation like this on 235 | 236 | ![1B9A0B40-1FF8-4B37-8D69-1BA737A097EA](https://user-images.githubusercontent.com/36472486/93882213-2dd48e00-fce0-11ea-8c3a-d1a85f7e5800.jpeg) 237 | ![CEFDA1D6-094A-4C3F-AB9F-06465707CAFF](https://user-images.githubusercontent.com/36472486/93879140-7e95b800-fcdb-11ea-8076-61d9b694cb3f.jpeg) 238 | ![2C4047E5-7F17-4CF5-8D06-83361184C914](https://user-images.githubusercontent.com/36472486/93879146-7fc6e500-fcdb-11ea-828c-fd3bb97c6e8a.jpeg) 239 | ![7F8A4DEA-49BA-4557-865D-7582D5E67C26](https://user-images.githubusercontent.com/36472486/93879150-80f81200-fcdb-11ea-8c0b-d32e133c27de.jpeg) 240 | ![F2278990-D81C-4A01-86A9-8D85F9C95032](https://user-images.githubusercontent.com/36472486/93879153-8190a880-fcdb-11ea-9145-dcb31f433766.jpeg) 241 | ![6CF720DC-4F9B-4B20-B1EF-DF169F405CE9](https://user-images.githubusercontent.com/36472486/93879154-82293f00-fcdb-11ea-807f-c030c79cb4db.png) 242 | ![70A2AC60-AFFC-467C-B294-A0A93D253DBE](https://user-images.githubusercontent.com/36472486/93879155-835a6c00-fcdb-11ea-9188-ddc51f44dbf5.png) 243 | 244 | ``` 245 | ########################################################################## 246 | # Control Watercontrol with NFC tag 247 | ########################################################################## 248 | 249 | - id: '1600768999472' 250 | alias: 'NFC Tag Garden watering on/off is scanned if water is off' 251 | description: If the irrigation in the garden is off start watering 252 | trigger: 253 | - platform: tag 254 | tag_id: 9dc6d5b1-651d-4880-839c-19cdd798a5f8 255 | condition: 256 | - condition: device 257 | type: is_off 258 | device_id: eecdf62964f3494d877413f7bd7b2a45 259 | entity_id: switch.garten_water_control 260 | domain: switch 261 | action: 262 | - type: turn_on 263 | device_id: eecdf62964f3494d877413f7bd7b2a45 264 | entity_id: switch.garten_water_control 265 | domain: switch 266 | mode: single 267 | 268 | - id: '1600769207834' 269 | alias: 'NFC Tag Garden watering on/off is scanned if water is on' 270 | description: If the irrigation in the garden is on stop watering 271 | trigger: 272 | - platform: tag 273 | tag_id: 9dc6d5b1-651d-4880-839c-19cdd798a5f8 274 | condition: 275 | - condition: device 276 | type: is_on 277 | device_id: eecdf62964f3494d877413f7bd7b2a45 278 | entity_id: switch.garten_water_control 279 | domain: switch 280 | action: 281 | - type: turn_off 282 | device_id: eecdf62964f3494d877413f7bd7b2a45 283 | entity_id: switch.garten_water_control 284 | domain: switch 285 | mode: single``` 286 | 287 | 288 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for Gardena Smart System devices.""" 2 | import asyncio 3 | import logging 4 | 5 | from gardena.exceptions.authentication_exception import AuthenticationException 6 | from gardena.smart_system import SmartSystem, get_ssl_context 7 | from homeassistant.config_entries import ConfigEntry 8 | from homeassistant.const import ( 9 | CONF_CLIENT_ID, 10 | CONF_CLIENT_SECRET, 11 | EVENT_HOMEASSISTANT_STOP, 12 | ) 13 | from homeassistant.core import HomeAssistant 14 | from oauthlib.oauth2.rfc6749.errors import ( 15 | AccessDeniedError, 16 | InvalidClientError, 17 | MissingTokenError, 18 | ) 19 | 20 | from .const import ( 21 | DOMAIN, 22 | GARDENA_LOCATION, 23 | GARDENA_SYSTEM, 24 | ) 25 | 26 | _LOGGER = logging.getLogger(__name__) 27 | 28 | PLATFORMS = ["lawn_mower", "sensor", "switch", "binary_sensor", "button"] 29 | 30 | # Create SSL context outside of event loop 31 | _SSL_CONTEXT = get_ssl_context() 32 | 33 | async def async_setup(hass: HomeAssistant, config: dict): 34 | """Set up the Gardena Smart System integration.""" 35 | if DOMAIN not in hass.data: 36 | hass.data[DOMAIN] = {} 37 | return True 38 | 39 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 40 | _LOGGER.debug("Setting up Gardena Smart System component") 41 | 42 | gardena_system = GardenaSmartSystem( 43 | hass, 44 | client_id=entry.data[CONF_CLIENT_ID], 45 | client_secret=entry.data[CONF_CLIENT_SECRET], 46 | ) 47 | while True: 48 | try: 49 | await gardena_system.start() 50 | break # If connection is successful, return True 51 | except ConnectionError: 52 | await asyncio.sleep(60) # Wait for 60 seconds before trying to reconnect 53 | except AccessDeniedError as ex: 54 | _LOGGER.error('Got Access Denied Error when setting up Gardena Smart System: %s', ex) 55 | return False 56 | except InvalidClientError as ex: 57 | _LOGGER.error('Got Invalid Client Error when setting up Gardena Smart System: %s', ex) 58 | return False 59 | except MissingTokenError as ex: 60 | _LOGGER.error('Got Missing Token Error when setting up Gardena Smart System: %s', ex) 61 | return False 62 | 63 | hass.data[DOMAIN][GARDENA_SYSTEM] = gardena_system 64 | 65 | hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: hass.async_create_task(gardena_system.stop())) 66 | 67 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 68 | 69 | _LOGGER.debug("Gardena Smart System component setup finished") 70 | return True 71 | 72 | 73 | class GardenaSmartSystem: 74 | """A Gardena Smart System wrapper class.""" 75 | 76 | def __init__(self, hass, client_id, client_secret): 77 | """Initialize the Gardena Smart System.""" 78 | self._hass = hass 79 | self.smart_system = SmartSystem( 80 | client_id=client_id, 81 | client_secret=client_secret, 82 | ssl_context=_SSL_CONTEXT # Use the pre-created SSL context 83 | ) 84 | 85 | async def start(self): 86 | try: 87 | _LOGGER.debug("Starting GardenaSmartSystem") 88 | await self.smart_system.authenticate() 89 | await self.smart_system.update_locations() 90 | 91 | if len(self.smart_system.locations) < 1: 92 | _LOGGER.error("No locations found") 93 | raise Exception("No locations found") 94 | 95 | # currently gardena supports only one location and gateway, so we can take the first 96 | location = list(self.smart_system.locations.values())[0] 97 | _LOGGER.debug(f"Using location: {location.name} ({location.id})") 98 | await self.smart_system.update_devices(location) 99 | self._hass.data[DOMAIN][GARDENA_LOCATION] = location 100 | _LOGGER.debug("Starting GardenaSmartSystem websocket") 101 | asyncio.create_task(self.smart_system.start_ws(self._hass.data[DOMAIN][GARDENA_LOCATION])) 102 | _LOGGER.debug("Websocket thread launched !") 103 | except AuthenticationException as ex: 104 | _LOGGER.error( 105 | f"Authentication failed : {ex.message}. You may need to check your token or create a new app in the gardena api and use the new token.") 106 | 107 | async def stop(self): 108 | _LOGGER.debug("Stopping GardenaSmartSystem") 109 | await self.smart_system.quit() 110 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Support for Gardena Smart System websocket connection status.""" 2 | from homeassistant.components.binary_sensor import ( 3 | BinarySensorDeviceClass, 4 | BinarySensorEntity, 5 | ) 6 | 7 | from custom_components.gardena_smart_system import GARDENA_SYSTEM 8 | 9 | from .const import DOMAIN 10 | 11 | 12 | async def async_setup_entry(hass, config_entry, async_add_entities): 13 | """Perform the setup for Gardena websocket connection status.""" 14 | async_add_entities( 15 | [SmartSystemWebsocketStatus(hass.data[DOMAIN][GARDENA_SYSTEM].smart_system)], 16 | True, 17 | ) 18 | 19 | 20 | class SmartSystemWebsocketStatus(BinarySensorEntity): 21 | """Representation of Gardena Smart System websocket connection status.""" 22 | 23 | def __init__(self, smart_system) -> None: 24 | """Initialize the binary sensor.""" 25 | super().__init__() 26 | self._unique_id = "smart_gardena_websocket_status" 27 | self._name = "Gardena Smart System connection" 28 | self._smart_system = smart_system 29 | 30 | async def async_added_to_hass(self): 31 | """Subscribe to events.""" 32 | self._smart_system.add_ws_status_callback(self.update_callback) 33 | 34 | @property 35 | def name(self): 36 | """Return the name of the device.""" 37 | return self._name 38 | 39 | @property 40 | def unique_id(self) -> str: 41 | """Return a unique ID.""" 42 | return self._unique_id 43 | 44 | @property 45 | def is_on(self) -> bool: 46 | """Return the status of the sensor.""" 47 | return self._smart_system.is_ws_connected 48 | 49 | @property 50 | def should_poll(self) -> bool: 51 | """No polling needed for a sensor.""" 52 | return False 53 | 54 | def update_callback(self, status): 55 | """Call update for Home Assistant when the device is updated.""" 56 | self.schedule_update_ha_state(True) 57 | 58 | @property 59 | def device_class(self): 60 | """Return the class of this device, from component DEVICE_CLASSES.""" 61 | return BinarySensorDeviceClass.CONNECTIVITY 62 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/button.py: -------------------------------------------------------------------------------- 1 | """Support for Gardena Smart System buttons.""" 2 | import logging 3 | 4 | from homeassistant.components.button import ButtonEntity 5 | from homeassistant.core import callback 6 | from homeassistant.helpers import entity_platform 7 | 8 | from .const import ( 9 | DOMAIN, 10 | GARDENA_LOCATION, 11 | ) 12 | 13 | _LOGGER = logging.getLogger(__name__) 14 | 15 | async def async_setup_entry(hass, config_entry, async_add_entities): 16 | """Set up the Gardena Smart System buttons.""" 17 | entities = [] 18 | for mower in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("MOWER"): 19 | entities.append(GardenaStartOverrideButton(mower, config_entry.options)) 20 | entities.append(GardenaReturnToDockButton(mower)) 21 | 22 | _LOGGER.debug("Adding mower buttons: %s", entities) 23 | async_add_entities(entities, True) 24 | 25 | 26 | class GardenaStartOverrideButton(ButtonEntity): 27 | """Representation of a Gardena Start Override button.""" 28 | 29 | def __init__(self, mower, options): 30 | """Initialize the Gardena Start Override button.""" 31 | self._device = mower 32 | self._options = options 33 | self._name = f"{self._device.name} Start Override" 34 | self._unique_id = f"{self._device.serial}-start-override" 35 | 36 | async def async_added_to_hass(self): 37 | """Subscribe to events.""" 38 | self._device.add_callback(self.update_callback) 39 | 40 | @property 41 | def should_poll(self) -> bool: 42 | """No polling needed for a button.""" 43 | return False 44 | 45 | def update_callback(self, device): 46 | """Call update for Home Assistant when the device is updated.""" 47 | self.schedule_update_ha_state(True) 48 | 49 | @property 50 | def name(self): 51 | """Return the name of the device.""" 52 | return self._name 53 | 54 | @property 55 | def unique_id(self) -> str: 56 | """Return a unique ID.""" 57 | return self._unique_id 58 | 59 | @property 60 | def available(self): 61 | """Return True if the device is available.""" 62 | return self._device.state != "UNAVAILABLE" 63 | 64 | @property 65 | def device_info(self): 66 | """Return device info.""" 67 | return { 68 | "identifiers": { 69 | (DOMAIN, self._device.serial) 70 | }, 71 | "name": self._device.name, 72 | "manufacturer": "Gardena", 73 | "model": self._device.model_type, 74 | } 75 | 76 | async def async_press(self) -> None: 77 | """Handle the button press.""" 78 | duration = self._options.get("mower_duration", 60) * 60 # Convert minutes to seconds 79 | await self._device.start_seconds_to_override(duration) 80 | 81 | 82 | class GardenaReturnToDockButton(ButtonEntity): 83 | """Representation of a Gardena Return to Dock button.""" 84 | 85 | def __init__(self, mower): 86 | """Initialize the Gardena Return to Dock button.""" 87 | self._device = mower 88 | self._name = f"{self._device.name} Return to Dock" 89 | self._unique_id = f"{self._device.serial}-return-to-dock" 90 | 91 | async def async_added_to_hass(self): 92 | """Subscribe to events.""" 93 | self._device.add_callback(self.update_callback) 94 | 95 | @property 96 | def should_poll(self) -> bool: 97 | """No polling needed for a button.""" 98 | return False 99 | 100 | def update_callback(self, device): 101 | """Call update for Home Assistant when the device is updated.""" 102 | self.schedule_update_ha_state(True) 103 | 104 | @property 105 | def name(self): 106 | """Return the name of the device.""" 107 | return self._name 108 | 109 | @property 110 | def unique_id(self) -> str: 111 | """Return a unique ID.""" 112 | return self._unique_id 113 | 114 | @property 115 | def available(self): 116 | """Return True if the device is available.""" 117 | return self._device.state != "UNAVAILABLE" 118 | 119 | @property 120 | def device_info(self): 121 | """Return device info.""" 122 | return { 123 | "identifiers": { 124 | (DOMAIN, self._device.serial) 125 | }, 126 | "name": self._device.name, 127 | "manufacturer": "Gardena", 128 | "model": self._device.model_type, 129 | } 130 | 131 | async def async_press(self) -> None: 132 | """Handle the button press.""" 133 | await self._device.park_until_next_task() -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/config_flow.py: -------------------------------------------------------------------------------- 1 | """Config flow for Gardena integration.""" 2 | import logging 3 | from collections import OrderedDict 4 | 5 | import homeassistant.helpers.config_validation as cv 6 | import voluptuous as vol 7 | from gardena.smart_system import SmartSystem 8 | from homeassistant import config_entries 9 | from homeassistant.const import ( 10 | CONF_CLIENT_ID, 11 | CONF_CLIENT_SECRET, 12 | CONF_ID, 13 | ) 14 | from homeassistant.core import callback 15 | 16 | from .const import ( 17 | DOMAIN, 18 | CONF_MOWER_DURATION, 19 | CONF_SMART_IRRIGATION_DURATION, 20 | CONF_SMART_WATERING_DURATION, 21 | DEFAULT_MOWER_DURATION, 22 | DEFAULT_SMART_IRRIGATION_DURATION, 23 | DEFAULT_SMART_WATERING_DURATION, 24 | ) 25 | 26 | _LOGGER = logging.getLogger(__name__) 27 | 28 | DEFAULT_OPTIONS = { 29 | CONF_MOWER_DURATION: DEFAULT_MOWER_DURATION, 30 | CONF_SMART_IRRIGATION_DURATION: DEFAULT_SMART_IRRIGATION_DURATION, 31 | CONF_SMART_WATERING_DURATION: DEFAULT_SMART_WATERING_DURATION, 32 | } 33 | 34 | 35 | class GardenaSmartSystemConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 36 | """Handle a config flow for Gardena.""" 37 | 38 | VERSION = 1 39 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH 40 | 41 | async def _show_setup_form(self, errors=None): 42 | """Show the setup form to the user.""" 43 | errors = {} 44 | 45 | fields = OrderedDict() 46 | fields[vol.Required(CONF_CLIENT_ID)] = str 47 | fields[vol.Required(CONF_CLIENT_SECRET)] = str 48 | 49 | return self.async_show_form( 50 | step_id="user", data_schema=vol.Schema(fields), errors=errors 51 | ) 52 | 53 | async def async_step_user(self, user_input=None): 54 | """Handle the initial step.""" 55 | if user_input is None: 56 | return await self._show_setup_form() 57 | 58 | errors = {} 59 | # try: 60 | # await try_connection( 61 | # user_input[CONF_CLIENT_ID], 62 | # user_input[CONF_CLIENT_SECRET]) 63 | # except Exception: # pylint: disable=broad-except 64 | # _LOGGER.exception("Unexpected exception") 65 | # errors["base"] = "unknown" 66 | # return await self._show_setup_form(errors) 67 | 68 | unique_id = user_input[CONF_CLIENT_ID] 69 | 70 | await self.async_set_unique_id(unique_id) 71 | self._abort_if_unique_id_configured() 72 | 73 | return self.async_create_entry( 74 | title="", 75 | data={ 76 | CONF_ID: unique_id, 77 | CONF_CLIENT_ID: user_input[CONF_CLIENT_ID], 78 | CONF_CLIENT_SECRET: user_input[CONF_CLIENT_SECRET] 79 | }) 80 | 81 | @staticmethod 82 | @callback 83 | def async_get_options_flow(config_entry): 84 | return GardenaSmartSystemOptionsFlowHandler(config_entry) 85 | 86 | 87 | class GardenaSmartSystemOptionsFlowHandler(config_entries.OptionsFlow): 88 | def __init__(self, config_entry): 89 | """Initialize Gardena Smart System options flow.""" 90 | super().__init__() 91 | 92 | async def async_step_init(self, user_input=None): 93 | """Manage the options.""" 94 | return await self.async_step_user() 95 | 96 | async def async_step_user(self, user_input=None): 97 | """Handle a flow initialized by the user.""" 98 | errors = {} 99 | if user_input is not None: 100 | # TODO: Validate options (min, max values) 101 | return self.async_create_entry(title="", data=user_input) 102 | 103 | fields = OrderedDict() 104 | fields[vol.Optional( 105 | CONF_MOWER_DURATION, 106 | default=self.config_entry.options.get( 107 | CONF_MOWER_DURATION, DEFAULT_MOWER_DURATION))] = cv.positive_int 108 | fields[vol.Optional( 109 | CONF_SMART_IRRIGATION_DURATION, 110 | default=self.config_entry.options.get( 111 | CONF_SMART_IRRIGATION_DURATION, DEFAULT_SMART_IRRIGATION_DURATION))] = cv.positive_int 112 | fields[vol.Optional( 113 | CONF_SMART_WATERING_DURATION, 114 | default=self.config_entry.options.get( 115 | CONF_SMART_WATERING_DURATION, DEFAULT_SMART_WATERING_DURATION))] = cv.positive_int 116 | 117 | return self.async_show_form(step_id="user", data_schema=vol.Schema(fields), errors=errors) 118 | 119 | 120 | async def try_connection(client_id, client_secret): 121 | _LOGGER.debug("Trying to connect to Gardena during setup") 122 | smart_system = SmartSystem(client_id=client_id, client_secret=client_secret) 123 | await smart_system.authenticate() 124 | await smart_system.update_locations() 125 | await smart_system.quit() 126 | _LOGGER.debug("Successfully connected to Gardena during setup") 127 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/const.py: -------------------------------------------------------------------------------- 1 | DOMAIN = "gardena_smart_system" 2 | GARDENA_SYSTEM = "gardena_system" 3 | GARDENA_LOCATION = "gardena_location" 4 | 5 | CONF_MOWER_DURATION = "mower_duration" 6 | CONF_SMART_IRRIGATION_DURATION = "smart_irrigation_control_duration" 7 | CONF_SMART_WATERING_DURATION = "smart_watering_duration" 8 | 9 | DEFAULT_MOWER_DURATION = 60 10 | DEFAULT_SMART_IRRIGATION_DURATION = 30 11 | DEFAULT_SMART_WATERING_DURATION = 30 12 | 13 | ATTR_NAME = "name" 14 | ATTR_ACTIVITY = "activity" 15 | ATTR_BATTERY_STATE = "battery_state" 16 | ATTR_RF_LINK_LEVEL = "rf_link_level" 17 | ATTR_RF_LINK_STATE = "rf_link_state" 18 | ATTR_SERIAL = "serial" 19 | ATTR_OPERATING_HOURS = "operating_hours" 20 | ATTR_LAST_ERROR = "last_error" 21 | ATTR_ERROR = "error" 22 | ATTR_STATE = "state" 23 | ATTR_STINT_START = "stint_start" 24 | ATTR_STINT_END = "stint_end" 25 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/lawn_mower.py: -------------------------------------------------------------------------------- 1 | """Support for Gardena mower.""" 2 | import asyncio 3 | import logging 4 | from datetime import datetime, timedelta 5 | 6 | import voluptuous as vol 7 | 8 | from homeassistant.const import ( 9 | ATTR_BATTERY_LEVEL, 10 | ) 11 | from homeassistant.components.lawn_mower import ( 12 | LawnMowerEntity, 13 | LawnMowerEntityFeature, 14 | LawnMowerActivity 15 | ) 16 | from homeassistant.helpers import config_validation as cv, entity_platform 17 | from .const import ( 18 | ATTR_ACTIVITY, 19 | ATTR_BATTERY_STATE, 20 | ATTR_NAME, 21 | ATTR_OPERATING_HOURS, 22 | ATTR_RF_LINK_LEVEL, 23 | ATTR_RF_LINK_STATE, 24 | ATTR_SERIAL, 25 | ATTR_LAST_ERROR, 26 | ATTR_ERROR, 27 | ATTR_STATE, 28 | ATTR_STINT_START, 29 | ATTR_STINT_END, 30 | CONF_MOWER_DURATION, 31 | DEFAULT_MOWER_DURATION, 32 | DOMAIN, 33 | GARDENA_LOCATION, 34 | ) 35 | 36 | 37 | _LOGGER = logging.getLogger(__name__) 38 | 39 | SCAN_INTERVAL = timedelta(minutes=1) 40 | 41 | SUPPORT_GARDENA = ( 42 | LawnMowerEntityFeature.START_MOWING | 43 | LawnMowerEntityFeature.PAUSE | 44 | LawnMowerEntityFeature.DOCK 45 | ) 46 | 47 | 48 | async def async_setup_entry(hass, config_entry, async_add_entities): 49 | """Set up the Gardena smart mower system.""" 50 | entities = [] 51 | for mower in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("MOWER"): 52 | entities.append(GardenaSmartMowerLawnMowerEntity(hass, mower, config_entry.options)) 53 | 54 | _LOGGER.debug("Adding mower as lawn_mower: %s", entities) 55 | async_add_entities(entities, True) 56 | 57 | platform = entity_platform.async_get_current_platform() 58 | platform.async_register_entity_service( 59 | "start_override", 60 | { 61 | vol.Required("duration"): cv.positive_int 62 | }, 63 | "async_start_override", 64 | ) 65 | 66 | 67 | class GardenaSmartMowerLawnMowerEntity(LawnMowerEntity): 68 | """Representation of a Gardena Connected Mower.""" 69 | 70 | def __init__(self, hass, mower, options): 71 | """Initialize the Gardena Connected Mower.""" 72 | self.hass = hass 73 | self._device = mower 74 | self._options = options 75 | self._name = "{}".format(self._device.name) 76 | self._unique_id = f"{self._device.serial}-mower" 77 | self._activity = None 78 | self._error_message = "" 79 | self._stint_start = None 80 | self._stint_end = None 81 | 82 | async def async_added_to_hass(self): 83 | """Subscribe to events.""" 84 | self._device.add_callback(self.update_callback) 85 | 86 | @property 87 | def should_poll(self) -> bool: 88 | """No polling needed for a lawn_mower.""" 89 | return False 90 | 91 | @property 92 | def activity(self) -> LawnMowerActivity: 93 | """Return the state of the mower.""" 94 | return self._activity 95 | 96 | def update_callback(self, device): 97 | """Call update for Home Assistant when the device is updated.""" 98 | self.schedule_update_ha_state(True) 99 | 100 | async def async_update(self): 101 | """Update the states of Gardena devices.""" 102 | _LOGGER.debug("Running Gardena update") 103 | # Managing state 104 | state = self._device.state 105 | _LOGGER.debug("Mower has state %s", state) 106 | if state in ["WARNING", "ERROR", "UNAVAILABLE"]: 107 | self._error_message = self._device.last_error_code 108 | if self._device.last_error_code == "PARKED_DAILY_LIMIT_REACHED": 109 | self._activity = LawnMowerActivity.DOCKED 110 | else: 111 | _LOGGER.debug("Mower has an error") 112 | self._activity = LawnMowerActivity.ERROR 113 | else: 114 | _LOGGER.debug("Getting mower state") 115 | activity = self._device.activity 116 | _LOGGER.debug("Mower has activity %s", activity) 117 | if activity in ["PAUSED", "PAUSED_IN_CS"]: 118 | self._activity = LawnMowerActivity.PAUSED 119 | elif activity in [ 120 | "OK_CUTTING", 121 | "OK_CUTTING_TIMER_OVERRIDDEN", 122 | "OK_LEAVING", 123 | ]: 124 | if self._activity != LawnMowerActivity.MOWING: 125 | self._stint_start = datetime.now() 126 | self._stint_end = None 127 | self._activity = LawnMowerActivity.MOWING 128 | elif activity in ["OK_SEARCHING", "INITIATE_NEXT_ACTION"]: 129 | if self._activity == LawnMowerActivity.MOWING: 130 | self._stint_end = datetime.now() 131 | self._activity = LawnMowerActivity.RETURNING 132 | elif activity in [ 133 | "OK_CHARGING", 134 | "PARKED_TIMER", 135 | "PARKED_PARK_SELECTED", 136 | "PARKED_AUTOTIMER", 137 | "PARKED_FROST", 138 | "STOPPED_IN_GARDEN", 139 | "SEARCHING_FOR_SATELLITES", 140 | ]: 141 | self._activity = LawnMowerActivity.DOCKED 142 | elif activity == "NONE": 143 | self._activity = None 144 | _LOGGER.debug("Mower has no activity") 145 | 146 | @property 147 | def name(self): 148 | """Return the name of the device.""" 149 | return self._device.name 150 | 151 | @property 152 | def supported_features(self): 153 | """Flag lawn mower robot features that are supported.""" 154 | return SUPPORT_GARDENA 155 | 156 | @property 157 | def battery_level(self): 158 | """Return the battery level of the lawn mower.""" 159 | return self._device.battery_level 160 | 161 | @property 162 | def available(self): 163 | """Return True if the device is available.""" 164 | return self._device.state != "UNAVAILABLE" 165 | 166 | def error(self): 167 | """Return the error message.""" 168 | if self._activity == LawnMowerActivity.ERROR: 169 | return self._error_message 170 | return "" 171 | 172 | @property 173 | def extra_state_attributes(self): 174 | """Return the state attributes of the lawn mower.""" 175 | return { 176 | ATTR_ACTIVITY: self._device.activity, 177 | ATTR_BATTERY_LEVEL: self._device.battery_level, 178 | ATTR_BATTERY_STATE: self._device.battery_state, 179 | ATTR_RF_LINK_LEVEL: self._device.rf_link_level, 180 | ATTR_RF_LINK_STATE: self._device.rf_link_state, 181 | ATTR_OPERATING_HOURS: self._device.operating_hours, 182 | ATTR_LAST_ERROR: self._device.last_error_code, 183 | ATTR_ERROR: "NONE" if self._device.activity != "NONE" else self._device.last_error_code, 184 | ATTR_STATE: self._device.activity if self._device.activity != "NONE" else self._device.last_error_code, 185 | ATTR_STINT_START: self._stint_start, 186 | ATTR_STINT_END: self._stint_end 187 | } 188 | 189 | @property 190 | def option_mower_duration(self) -> int: 191 | return self._options.get(CONF_MOWER_DURATION, DEFAULT_MOWER_DURATION) 192 | 193 | async def async_start_mowing(self) -> None: 194 | """Resume schedule.""" 195 | await self._device.start_dont_override() 196 | 197 | async def async_dock(self) -> None: 198 | """Parks the mower until next schedule.""" 199 | await self._device.park_until_next_task() 200 | 201 | async def async_pause(self) -> None: 202 | """Parks the mower until further notice.""" 203 | await self._device.park_until_further_notice() 204 | 205 | async def async_start_override( 206 | self, duration: int 207 | ) -> None: 208 | """Start the mower using Gardena API command START_SECONDS_TO_OVERRIDE.""" 209 | await self._device.start_seconds_to_override(duration) 210 | 211 | @property 212 | def unique_id(self) -> str: 213 | """Return a unique ID.""" 214 | return self._unique_id 215 | 216 | @property 217 | def device_info(self): 218 | return { 219 | "identifiers": { 220 | # Serial numbers are unique identifiers within a specific domain 221 | (DOMAIN, self._device.serial) 222 | }, 223 | "name": self._device.name, 224 | "manufacturer": "Gardena", 225 | "model": self._device.model_type, 226 | } 227 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "gardena_smart_system", 3 | "name": "Gardena Smart System integration", 4 | "version": "1.3.0", 5 | "config_flow": true, 6 | "documentation": "https://github.com/py-smart-gardena/hass-gardena-smart-system", 7 | "issue_tracker": "https://github.com/py-smart-gardena/hass-gardena-smart-system/issues", 8 | "dependencies": [], 9 | "codeowners": ["@py-smart-gardena"], 10 | "requirements": ["py-smart-gardena==1.3.14", "oauthlib==3.2.2"] 11 | } 12 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/sensor.py: -------------------------------------------------------------------------------- 1 | """Support for Gardena Smart System sensors.""" 2 | import logging 3 | 4 | from homeassistant.components.sensor import SensorDeviceClass, UnitOfTemperature 5 | 6 | from homeassistant.helpers.entity import Entity 7 | from homeassistant.const import ( 8 | ATTR_BATTERY_LEVEL, 9 | PERCENTAGE, 10 | ) 11 | 12 | from .const import ( 13 | DOMAIN, 14 | ATTR_BATTERY_STATE, 15 | ATTR_RF_LINK_LEVEL, 16 | ATTR_RF_LINK_STATE, 17 | GARDENA_LOCATION, 18 | ) 19 | 20 | 21 | _LOGGER = logging.getLogger(__name__) 22 | 23 | SOIL_SENSOR_TYPES = { 24 | "soil_temperature": [ 25 | UnitOfTemperature.CELSIUS, 26 | "mdi:thermometer", 27 | SensorDeviceClass.TEMPERATURE, 28 | ], 29 | "soil_humidity": ["%", "mdi:water-percent", SensorDeviceClass.HUMIDITY], 30 | ATTR_BATTERY_LEVEL: [PERCENTAGE, "mdi:battery", SensorDeviceClass.BATTERY], 31 | } 32 | 33 | SENSOR_TYPES = { 34 | **{ 35 | "ambient_temperature": [ 36 | UnitOfTemperature.CELSIUS, 37 | "mdi:thermometer", 38 | SensorDeviceClass.TEMPERATURE, 39 | ], 40 | "light_intensity": ["lx", None, SensorDeviceClass.ILLUMINANCE], 41 | }, 42 | **SOIL_SENSOR_TYPES, 43 | } 44 | 45 | 46 | async def async_setup_entry(hass, config_entry, async_add_entities): 47 | """Perform the setup for Gardena sensor devices.""" 48 | entities = [] 49 | for sensor in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("SENSOR"): 50 | for sensor_type in SENSOR_TYPES: 51 | entities.append(GardenaSensor(sensor, sensor_type)) 52 | 53 | for sensor in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type( 54 | "SOIL_SENSOR" 55 | ): 56 | for sensor_type in SOIL_SENSOR_TYPES: 57 | entities.append(GardenaSensor(sensor, sensor_type)) 58 | 59 | for mower in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("MOWER"): 60 | # Add battery sensor for mower 61 | entities.append(GardenaSensor(mower, ATTR_BATTERY_LEVEL)) 62 | 63 | for water_control in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type( 64 | "WATER_CONTROL" 65 | ): 66 | # Add battery sensor for water control 67 | entities.append(GardenaSensor(water_control, ATTR_BATTERY_LEVEL)) 68 | _LOGGER.debug("Adding sensor as sensor %s", entities) 69 | async_add_entities(entities, True) 70 | 71 | 72 | class GardenaSensor(Entity): 73 | """Representation of a Gardena Sensor.""" 74 | 75 | def __init__(self, device, sensor_type): 76 | """Initialize the Gardena Sensor.""" 77 | self._sensor_type = sensor_type 78 | self._name = f"{device.name} {sensor_type.replace('_', ' ')}" 79 | self._unique_id = f"{device.serial}-{sensor_type}" 80 | self._device = device 81 | 82 | async def async_added_to_hass(self): 83 | """Subscribe to sensor events.""" 84 | self._device.add_callback(self.update_callback) 85 | 86 | @property 87 | def should_poll(self) -> bool: 88 | """No polling needed for a sensor.""" 89 | return False 90 | 91 | def update_callback(self, device): 92 | """Call update for Home Assistant when the device is updated.""" 93 | self.schedule_update_ha_state(True) 94 | 95 | @property 96 | def name(self): 97 | """Return the name of the device.""" 98 | return self._name 99 | 100 | @property 101 | def unique_id(self) -> str: 102 | """Return a unique ID.""" 103 | return self._unique_id 104 | 105 | @property 106 | def icon(self): 107 | """Return the icon to use in the frontend.""" 108 | return SENSOR_TYPES[self._sensor_type][1] 109 | 110 | @property 111 | def unit_of_measurement(self): 112 | """Return the unit of measurement of this entity, if any.""" 113 | return SENSOR_TYPES[self._sensor_type][0] 114 | 115 | @property 116 | def device_class(self): 117 | """Return the device class of this entity.""" 118 | if self._sensor_type in SENSOR_TYPES: 119 | return SENSOR_TYPES[self._sensor_type][2] 120 | return None 121 | 122 | @property 123 | def state(self): 124 | """Return the state of the sensor.""" 125 | return getattr(self._device, self._sensor_type) 126 | 127 | @property 128 | def extra_state_attributes(self): 129 | """Return the state attributes of the sensor.""" 130 | return { 131 | ATTR_BATTERY_LEVEL: self._device.battery_level, 132 | ATTR_BATTERY_STATE: self._device.battery_state, 133 | ATTR_RF_LINK_LEVEL: self._device.rf_link_level, 134 | ATTR_RF_LINK_STATE: self._device.rf_link_state, 135 | } 136 | 137 | @property 138 | def device_info(self): 139 | return { 140 | "identifiers": { 141 | # Serial numbers are unique identifiers within a specific domain 142 | (DOMAIN, self._device.serial) 143 | }, 144 | "name": self._device.name, 145 | "manufacturer": "Gardena", 146 | "model": self._device.model_type, 147 | } 148 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/services.yaml: -------------------------------------------------------------------------------- 1 | start_override: 2 | target: 3 | entity: 4 | integration: "gardena_smart_system" 5 | domain: "lawn_mower" 6 | fields: 7 | duration: 8 | required: true 9 | example: "3600" 10 | default: "3600" 11 | selector: 12 | number: 13 | min: 1 14 | max: 86400 15 | unit_of_measurement: seconds 16 | mode: box -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" 5 | }, 6 | "error": { 7 | "cannot_connect": "Failed to connect, please try again.", 8 | "invalid_auth": "Invalid authentication.", 9 | "too_many_requests": "Too many requests, retry later.", 10 | "unknown": "Unexpected error." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Please enter your credentials.", 15 | "data": { 16 | "client_id": "Application Key / Client ID", 17 | "client_secret": "Application secret / Client secret" 18 | }, 19 | "title": "Gardena Smart System" 20 | } 21 | } 22 | }, 23 | "options": { 24 | "step": { 25 | "user": { 26 | "data": { 27 | "mower_duration": "Mower Duration (minutes)", 28 | "smart_irrigation_control_duration": "Smart Irrigation Control Duration (minutes)", 29 | "smart_watering_duration": "Smart Watering Duration (minutes)" 30 | }, 31 | "title": "Gardena Smart System - Options" 32 | } 33 | } 34 | }, 35 | "services": { 36 | "start_override": { 37 | "name": "Start override", 38 | "description": "Start the mower immediately for specific duration and override the schedule.", 39 | "fields": { 40 | "duration": { 41 | "name": "Duration", 42 | "description": "The duration in seconds." 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/switch.py: -------------------------------------------------------------------------------- 1 | """Support for Gardena switch (Power control, water control, smart irrigation control).""" 2 | import asyncio 3 | import logging 4 | 5 | from homeassistant.core import callback 6 | from homeassistant.components.switch import SwitchEntity 7 | from homeassistant.const import ATTR_BATTERY_LEVEL 8 | 9 | from .const import ( 10 | ATTR_ACTIVITY, 11 | ATTR_BATTERY_STATE, 12 | ATTR_LAST_ERROR, 13 | ATTR_RF_LINK_LEVEL, 14 | ATTR_RF_LINK_STATE, 15 | ATTR_SERIAL, 16 | CONF_SMART_IRRIGATION_DURATION, 17 | CONF_SMART_WATERING_DURATION, 18 | DEFAULT_SMART_IRRIGATION_DURATION, 19 | DEFAULT_SMART_WATERING_DURATION, 20 | DOMAIN, 21 | GARDENA_LOCATION, 22 | ) 23 | from .sensor import GardenaSensor 24 | 25 | 26 | _LOGGER = logging.getLogger(__name__) 27 | 28 | 29 | async def async_setup_entry(hass, config_entry, async_add_entities): 30 | """Set up the switches platform.""" 31 | 32 | entities = [] 33 | for water_control in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("WATER_CONTROL"): 34 | entities.append(GardenaSmartWaterControl(water_control, config_entry.options)) 35 | 36 | for power_switch in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("POWER_SOCKET"): 37 | entities.append(GardenaPowerSocket(power_switch)) 38 | 39 | for smart_irrigation in hass.data[DOMAIN][GARDENA_LOCATION].find_device_by_type("SMART_IRRIGATION_CONTROL"): 40 | for valve in smart_irrigation.valves.values(): 41 | entities.append(GardenaSmartIrrigationControl( 42 | smart_irrigation, valve['id'], config_entry.options)) 43 | 44 | _LOGGER.debug( 45 | "Adding water control, power socket and smart irrigation control as switch: %s", 46 | entities) 47 | async_add_entities(entities, True) 48 | 49 | 50 | class GardenaSmartWaterControl(SwitchEntity): 51 | """Representation of a Gardena Smart Water Control.""" 52 | 53 | def __init__(self, wc, options): 54 | """Initialize the Gardena Smart Water Control.""" 55 | self._device = wc 56 | self._options = options 57 | self._name = f"{self._device.name}" 58 | self._unique_id = f"{self._device.serial}-valve" 59 | self._state = None 60 | self._error_message = "" 61 | 62 | async def async_added_to_hass(self): 63 | """Subscribe to events.""" 64 | self._device.add_callback(self.update_callback) 65 | 66 | @property 67 | def should_poll(self) -> bool: 68 | """No polling needed for a water valve.""" 69 | return False 70 | 71 | def update_callback(self, device): 72 | """Call update for Home Assistant when the device is updated.""" 73 | self.schedule_update_ha_state(True) 74 | 75 | async def async_update(self): 76 | """Update the states of Gardena devices.""" 77 | _LOGGER.debug("Running Gardena update") 78 | # Managing state 79 | state = self._device.valve_state 80 | _LOGGER.debug("Water control has state %s", state) 81 | if state in ["WARNING", "ERROR", "UNAVAILABLE"]: 82 | _LOGGER.debug("Water control has an error") 83 | self._state = False 84 | self._error_message = self._device.last_error_code 85 | else: 86 | _LOGGER.debug("Getting water control state") 87 | activity = self._device.valve_activity 88 | self._error_message = "" 89 | _LOGGER.debug("Water control has activity %s", activity) 90 | if activity == "CLOSED": 91 | self._state = False 92 | elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]: 93 | self._state = True 94 | else: 95 | _LOGGER.debug("Water control has none activity") 96 | 97 | @property 98 | def name(self): 99 | """Return the name of the device.""" 100 | return self._name 101 | 102 | @property 103 | def unique_id(self) -> str: 104 | """Return a unique ID.""" 105 | return self._unique_id 106 | 107 | @property 108 | def is_on(self): 109 | """Return true if it is on.""" 110 | return self._state 111 | 112 | @property 113 | def available(self): 114 | """Return True if the device is available.""" 115 | return self._device.valve_state != "UNAVAILABLE" 116 | 117 | def error(self): 118 | """Return the error message.""" 119 | return self._error_message 120 | 121 | @property 122 | def extra_state_attributes(self): 123 | """Return the state attributes of the water valve.""" 124 | return { 125 | ATTR_ACTIVITY: self._device.valve_activity, 126 | ATTR_BATTERY_LEVEL: self._device.battery_level, 127 | ATTR_BATTERY_STATE: self._device.battery_state, 128 | ATTR_RF_LINK_LEVEL: self._device.rf_link_level, 129 | ATTR_RF_LINK_STATE: self._device.rf_link_state, 130 | ATTR_LAST_ERROR: self._error_message, 131 | } 132 | 133 | @property 134 | def option_smart_watering_duration(self) -> int: 135 | return self._options.get( 136 | CONF_SMART_WATERING_DURATION, DEFAULT_SMART_WATERING_DURATION 137 | ) 138 | 139 | def turn_on(self, **kwargs): 140 | """Start watering.""" 141 | duration = self.option_smart_watering_duration * 60 142 | return asyncio.run_coroutine_threadsafe( 143 | self._device.start_seconds_to_override(duration), self.hass.loop 144 | ).result() 145 | 146 | def turn_off(self, **kwargs): 147 | """Stop watering.""" 148 | return asyncio.run_coroutine_threadsafe( 149 | self._device.stop_until_next_task(), self.hass.loop 150 | ).result() 151 | 152 | @property 153 | def device_info(self): 154 | return { 155 | "identifiers": { 156 | # Serial numbers are unique identifiers within a specific domain 157 | (DOMAIN, self._device.serial) 158 | }, 159 | "name": self._device.name, 160 | "manufacturer": "Gardena", 161 | "model": self._device.model_type, 162 | } 163 | 164 | 165 | class GardenaPowerSocket(SwitchEntity): 166 | """Representation of a Gardena Power Socket.""" 167 | 168 | def __init__(self, ps): 169 | """Initialize the Gardena Power Socket.""" 170 | self._device = ps 171 | self._name = f"{self._device.name}" 172 | self._unique_id = f"{self._device.serial}" 173 | self._state = None 174 | self._error_message = "" 175 | 176 | async def async_added_to_hass(self): 177 | """Subscribe to events.""" 178 | self._device.add_callback(self.update_callback) 179 | 180 | @property 181 | def should_poll(self) -> bool: 182 | """No polling needed for a power socket.""" 183 | return False 184 | 185 | def update_callback(self, device): 186 | """Call update for Home Assistant when the device is updated.""" 187 | self.schedule_update_ha_state(True) 188 | 189 | async def async_update(self): 190 | """Update the states of Gardena devices.""" 191 | _LOGGER.debug("Running Gardena update") 192 | # Managing state 193 | state = self._device.state 194 | _LOGGER.debug("Power socket has state %s", state) 195 | if state in ["WARNING", "ERROR", "UNAVAILABLE"]: 196 | _LOGGER.debug("Power socket has an error") 197 | self._state = False 198 | self._error_message = self._device.last_error_code 199 | else: 200 | _LOGGER.debug("Getting Power socket state") 201 | activity = self._device.activity 202 | self._error_message = "" 203 | _LOGGER.debug("Power socket has activity %s", activity) 204 | if activity == "OFF": 205 | self._state = False 206 | elif activity in ["FOREVER_ON", "TIME_LIMITED_ON", "SCHEDULED_ON"]: 207 | self._state = True 208 | else: 209 | _LOGGER.debug("Power socket has none activity") 210 | 211 | @property 212 | def name(self): 213 | """Return the name of the device.""" 214 | return self._name 215 | 216 | @property 217 | def unique_id(self) -> str: 218 | """Return a unique ID.""" 219 | return self._unique_id 220 | 221 | @property 222 | def is_on(self): 223 | """Return true if it is on.""" 224 | return self._state 225 | 226 | @property 227 | def available(self): 228 | """Return True if the device is available.""" 229 | return self._device.state != "UNAVAILABLE" 230 | 231 | def error(self): 232 | """Return the error message.""" 233 | return self._error_message 234 | 235 | @property 236 | def extra_state_attributes(self): 237 | """Return the state attributes of the power switch.""" 238 | return { 239 | ATTR_ACTIVITY: self._device.activity, 240 | ATTR_RF_LINK_LEVEL: self._device.rf_link_level, 241 | ATTR_RF_LINK_STATE: self._device.rf_link_state, 242 | ATTR_LAST_ERROR: self._error_message, 243 | } 244 | 245 | def turn_on(self, **kwargs): 246 | """Start watering.""" 247 | return asyncio.run_coroutine_threadsafe( 248 | self._device.start_override(), self.hass.loop 249 | ).result() 250 | 251 | def turn_off(self, **kwargs): 252 | """Stop watering.""" 253 | return asyncio.run_coroutine_threadsafe( 254 | self._device.stop_until_next_task(), self.hass.loop 255 | ).result() 256 | 257 | @property 258 | def device_info(self): 259 | return { 260 | "identifiers": { 261 | # Serial numbers are unique identifiers within a specific domain 262 | (DOMAIN, self._device.serial) 263 | }, 264 | "name": self._device.name, 265 | "manufacturer": "Gardena", 266 | "model": self._device.model_type, 267 | } 268 | 269 | 270 | class GardenaSmartIrrigationControl(SwitchEntity): 271 | """Representation of a Gardena Smart Irrigation Control.""" 272 | 273 | def __init__(self, sic, valve_id, options): 274 | """Initialize the Gardena Smart Irrigation Control.""" 275 | self._device = sic 276 | self._valve_id = valve_id 277 | self._options = options 278 | self._name = f"{self._device.name} - {self._device.valves[self._valve_id]['name']}" 279 | self._unique_id = f"{self._device.serial}-{self._valve_id}" 280 | self._state = None 281 | self._error_message = "" 282 | 283 | async def async_added_to_hass(self): 284 | """Subscribe to events.""" 285 | self._device.add_callback(self.update_callback) 286 | 287 | @property 288 | def should_poll(self) -> bool: 289 | """No polling needed for a smart irrigation control.""" 290 | return False 291 | 292 | def update_callback(self, device): 293 | """Call update for Home Assistant when the device is updated.""" 294 | self.schedule_update_ha_state(True) 295 | 296 | async def async_update(self): 297 | """Update the states of Gardena devices.""" 298 | _LOGGER.debug("Running Gardena update") 299 | # Managing state 300 | valve = self._device.valves[self._valve_id] 301 | _LOGGER.debug("Valve has state: %s", valve["state"]) 302 | if valve["state"] in ["WARNING", "ERROR", "UNAVAILABLE"]: 303 | _LOGGER.debug("Valve has an error") 304 | self._state = False 305 | self._error_message = valve["last_error_code"] 306 | else: 307 | _LOGGER.debug("Getting Valve state") 308 | activity = valve["activity"] 309 | self._error_message = "" 310 | _LOGGER.debug("Valve has activity: %s", activity) 311 | if activity == "CLOSED": 312 | self._state = False 313 | elif activity in ["MANUAL_WATERING", "SCHEDULED_WATERING"]: 314 | self._state = True 315 | else: 316 | _LOGGER.debug("Valve has unknown activity") 317 | 318 | @property 319 | def name(self): 320 | """Return the name of the device.""" 321 | return self._name 322 | 323 | @property 324 | def unique_id(self) -> str: 325 | """Return a unique ID.""" 326 | return self._unique_id 327 | 328 | @property 329 | def is_on(self): 330 | """Return true if it is on.""" 331 | return self._state 332 | 333 | @property 334 | def available(self): 335 | """Return True if the device is available.""" 336 | return self._device.valves[self._valve_id]["state"] != "UNAVAILABLE" 337 | 338 | def error(self): 339 | """Return the error message.""" 340 | return self._error_message 341 | 342 | @property 343 | def extra_state_attributes(self): 344 | """Return the state attributes of the smart irrigation control.""" 345 | return { 346 | ATTR_ACTIVITY: self._device.valves[self._valve_id]["activity"], 347 | ATTR_RF_LINK_LEVEL: self._device.rf_link_level, 348 | ATTR_RF_LINK_STATE: self._device.rf_link_state, 349 | ATTR_LAST_ERROR: self._error_message, 350 | } 351 | 352 | @property 353 | def option_smart_irrigation_duration(self) -> int: 354 | return self._options.get( 355 | CONF_SMART_IRRIGATION_DURATION, DEFAULT_SMART_IRRIGATION_DURATION 356 | ) 357 | 358 | def turn_on(self, **kwargs): 359 | """Start watering.""" 360 | duration = self.option_smart_irrigation_duration * 60 361 | return asyncio.run_coroutine_threadsafe( 362 | self._device.start_seconds_to_override(duration, self._valve_id), self.hass.loop 363 | ).result() 364 | 365 | def turn_off(self, **kwargs): 366 | """Stop watering.""" 367 | return asyncio.run_coroutine_threadsafe( 368 | self._device.stop_until_next_task(self._valve_id), self.hass.loop 369 | ).result() 370 | 371 | @property 372 | def device_info(self): 373 | return { 374 | "identifiers": { 375 | # Serial numbers are unique identifiers within a specific domain 376 | (DOMAIN, self._device.serial) 377 | }, 378 | "name": self._device.name, 379 | "manufacturer": "Gardena", 380 | "model": self._device.model_type, 381 | } 382 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Der Standort ist bereits konfiguriert" 5 | }, 6 | "error": { 7 | "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut.", 8 | "invalid_auth": "Ungültige Authentifizierung.", 9 | "too_many_requests": "Zu viele Anfragen, versuchen Sie es später erneut.", 10 | "unknown": "Unerwarteter Fehler." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Bitte geben Sie Ihre Anmeldeinformationen ein.", 15 | "data": { 16 | "email": "E-Mail", 17 | "password": "Passwort", 18 | "client_id": "Application Key / Client ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Mäherdauer (Minuten)", 29 | "smart_irrigation_control_duration": "Smart Irrigation Control Dauer (Minuten)", 30 | "smart_watering_duration": "Smarte Bewässerungs Dauer (Minuten)" 31 | }, 32 | "title": "Gardena Smart System - Optionen" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Überschreibung starten", 39 | "description": "Starten Sie den Mäher sofort für eine bestimmte Dauer und überschreiben Sie den Zeitplan.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Dauer", 44 | "description": "Die Dauer in Sekunden." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Location is already configured" 5 | }, 6 | "error": { 7 | "cannot_connect": "Failed to connect, please try again.", 8 | "invalid_auth": "Invalid authentication.", 9 | "too_many_requests": "Too many requests, retry later.", 10 | "unknown": "Unexpected error." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Please enter your credentials.", 15 | "data": { 16 | "email": "E-mail", 17 | "password": "Password", 18 | "client_id": "Application Key / Client ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Mower Duration (minutes)", 29 | "smart_irrigation_control_duration": "Smart Irrigation Control Duration (minutes)", 30 | "smart_watering_duration": "Smart Watering Duration (minutes)" 31 | }, 32 | "title": "Gardena Smart System - Options" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Start override", 39 | "description": "Start the mower immediately for specific duration and override the schedule.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Duration", 44 | "description": "The duration in seconds." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Sijainti on jo määritelty" 5 | }, 6 | "error": { 7 | "cannot_connect": "Yhteys epäonnistui, yritä uudelleen.", 8 | "invalid_auth": "Tunnistautuminen epäonnistui.", 9 | "too_many_requests": "Liian monta yritystä, yritä myöhemmin uudestaan.", 10 | "unknown": "Odottamaton virhe." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Syötä tunnistautumistiedot.", 15 | "data": { 16 | "email": "Sähköposti", 17 | "password": "Salasana", 18 | "client_id": "Application Key / Client ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Ruohonleikkuun kesto (minuuttia)", 29 | "smart_irrigation_control_duration": "Smart Irrigation Control kesto (minuuttia)", 30 | "smart_watering_duration": "Smart Watering Control kesto (minuuttia)" 31 | }, 32 | "title": "Gardena Smart System - valinnat" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Aloita ohitys", 39 | "description": "Käynnistä ruohonleikkuri välittömästi tietyksi ajaksi ja ohita aikataulu.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Kesto", 44 | "description": "Kesto sekunteina." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "La localisation est déjà configurée" 5 | }, 6 | "error": { 7 | "cannot_connect": "Impossible de se connecter, veuillez ré-essayer.", 8 | "invalid_auth": "Authentification invalide.", 9 | "too_many_requests": "Trop de requêtes, veuillez ré-essayer plus tard.", 10 | "unknown": "Erreur innatendue." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Veuillez renseigner vos identifiants.", 15 | "data": { 16 | "email": "E-mail", 17 | "password": "Mot de passe", 18 | "client_id": "Application Key / Client ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Durée de tonte (minutes)", 29 | "smart_irrigation_control_duration": "Durée de Smart Irrigation Control (minutes)", 30 | "smart_watering_duration": "Durée de Smart Watering (minutes)" 31 | }, 32 | "title": "Gardena Smart System - Options" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Démarrer la substitution", 39 | "description": "Démarrer la tondeuse immédiatement pour une durée spécifique et remplacer le planning.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Durée", 44 | "description": "La durée en secondes." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/nb.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Plasseringen er allerede konfigurert" 5 | }, 6 | "error": { 7 | "cannot_connect": "Kunne ikke koble til, prøv igjen.", 8 | "invalid_auth": "Ugyldig godkjenning", 9 | "too_many_requests": "For mange forespørsler, prøv senere.", 10 | "unknown": "Uventet feil." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Vennligst skriv inn legitimasjonen din.", 15 | "data": { 16 | "email": "E-post", 17 | "password": "Passord", 18 | "client_id": "Programnøkkel/klient-ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Klippevarighet (minutter)", 29 | "smart_irrigation_control_duration": "Smart vanningskontrollvarighet (minutter)", 30 | "smart_watering_duration": "Smart vanningstid (minutter)" 31 | }, 32 | "title": "Gardena Smart System - Alternativer" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Start overstyring", 39 | "description": "Start klipperen umiddelbart for en bestemt varighet og overstyr timeplanen.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Varighet", 44 | "description": "Varigheten i sekunder." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Locatie is reeds geconfigureerd." 5 | }, 6 | "error": { 7 | "cannot_connect": "Probleem tijdens connecteren, probeer het later opnieuw.", 8 | "invalid_auth": "Foutieve inloggegevens.", 9 | "too_many_requests": "Te veel aanvragen, probeer het later opnieuw.", 10 | "unknown": "Onverwachte fout." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Gelieve aan te melden.", 15 | "data": { 16 | "email": "E-mail", 17 | "password": "Wachtwoord", 18 | "client_id": "Application Key / Client ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Maaitijd (minuten)", 29 | "smart_irrigation_control_duration": "Smart Irrigation Control tijd (minuten)", 30 | "smart_watering_duration": "Smart Watering tijd (minuten)" 31 | }, 32 | "title": "Gardena Smart System - Opties" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Start overschrijving", 39 | "description": "Start de maaier onmiddellijk voor een specifieke duur en overschrijf het schema.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Duur", 44 | "description": "De duur in seconden." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Poloha je už nakonfigurovaná" 5 | }, 6 | "error": { 7 | "cannot_connect": "Nepodarilo sa pripojiť, skúste to znova.", 8 | "invalid_auth": "Neplatné overenie.", 9 | "too_many_requests": "Príliš veľa žiadostí, skúste to neskôr.", 10 | "unknown": "Neočakávaná chyba." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Zadajte svoje poverenia.", 15 | "data": { 16 | "email": "E-mail", 17 | "password": "Heslo", 18 | "client_id": "Aplikačný kľúč / ID klienta" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Trvanie mower (minúty)", 29 | "smart_irrigation_control_duration": "Trvanie inteligentného riadenia zavlažovania (minúty)", 30 | "smart_watering_duration": "Trvanie inteligentného zavlažovania (minúty)" 31 | }, 32 | "title": "Gardena Smart System - Možnosti" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Spustiť prepísanie", 39 | "description": "Okamžite spustite kosačku na určitý čas a prepíšte harmonogram.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Trvanie", 44 | "description": "Trvanie v sekundách." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Platsen är redan konfigurerad" 5 | }, 6 | "error": { 7 | "cannot_connect": "Det gick inte att ansluta, försök igen.", 8 | "invalid_auth": "Ogiltig autentisering.", 9 | "too_many_requests": "För många förfrågningar, försök igen senare.", 10 | "unknown": "Oväntat fel." 11 | }, 12 | "step": { 13 | "user": { 14 | "description": "Ange dina uppgifter", 15 | "data": { 16 | "email": "Epost", 17 | "password": "Lösenord", 18 | "client_id": "Application Key / Client ID" 19 | }, 20 | "title": "Gardena Smart System" 21 | } 22 | } 23 | }, 24 | "options": { 25 | "step": { 26 | "user": { 27 | "data": { 28 | "mower_duration": "Gräsklipparens varaktighet (minuter)", 29 | "smart_irrigation_control_duration": "Smart Irrigation Control varaktighet (minuter)", 30 | "smart_watering_duration": "Smart Watering varaktighet (minuter)" 31 | }, 32 | "title": "Gardena Smart System - valmöjligheter" 33 | } 34 | } 35 | }, 36 | "services": { 37 | "start_override": { 38 | "name": "Starta åsidosättning", 39 | "description": "Starta gräsklipparen omedelbart under en specifik tid och åsidosätt schemat.", 40 | "icon": "mdi:mower-on", 41 | "fields": { 42 | "duration": { 43 | "name": "Varaktighet", 44 | "description": "Varaktigheten i sekunder." 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.da.json: -------------------------------------------------------------------------------- 1 | { 2 | "device_automation": { 3 | "action_type": { 4 | "clean": "Lad {entity_name} klippe", 5 | "dock": "Lad {entity_name} vende tilbage til oplader" 6 | }, 7 | "condition_type": { 8 | "is_cleaning": "{entity_name} klipper", 9 | "is_docked": "{entity_name} er i oplader" 10 | }, 11 | "trigger_type": { 12 | "cleaning": "{entity_name} begyndte at klippe", 13 | "docked": "{entity_name} er i dock" 14 | } 15 | }, 16 | "state": { 17 | "_": { 18 | "cleaning": "Klipper", 19 | "docked": "I oplader", 20 | "error": "Fejl", 21 | "idle": "[%key:common::state::idle%]", 22 | "off": "[%key:common::state::off%]", 23 | "on": "[%key:common::state::on%]", 24 | "paused": "Sat p\u00e5 pause", 25 | "returning": "Vender tilbage til oplader" 26 | } 27 | }, 28 | "title": "Pl\u00e6neklipper" 29 | } 30 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Mäher", 3 | "device_automation": { 4 | "condition_type": { 5 | "is_docked": "{entity_name} ist angedockt", 6 | "is_cleaning": "{entity_name} mäht" 7 | }, 8 | "trigger_type": { 9 | "cleaning": "{entity_name} hat begonnen zu mähen", 10 | "docked": "{entity_name} hat angedockt" 11 | }, 12 | "action_type": { 13 | "clean": "Lasse {entity_name} mähen", 14 | "dock": "Lasse {entity_name} zum Dock zurückkehren" 15 | } 16 | }, 17 | "state": { 18 | "_": { 19 | "cleaning": "Mäht", 20 | "docked": "Angedockt", 21 | "error": "Fehler", 22 | "idle": "Inaktiv", 23 | "off": "Aus", 24 | "on": "An", 25 | "paused": "Pausiert", 26 | "returning": "Zurück zum Dock" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Mower", 3 | "device_automation": { 4 | "condition_type": { 5 | "is_docked": "{entity_name} is docked", 6 | "is_cleaning": "{entity_name} is cutting" 7 | }, 8 | "trigger_type": { 9 | "cleaning": "{entity_name} started cutting", 10 | "docked": "{entity_name} docked" 11 | }, 12 | "action_type": { 13 | "clean": "Let {entity_name} cut", 14 | "dock": "Let {entity_name} return to the dock" 15 | } 16 | }, 17 | "state": { 18 | "_": { 19 | "cleaning": "Cutting", 20 | "docked": "Docked", 21 | "error": "Error", 22 | "idle": "[%key:common::state::idle%]", 23 | "off": "[%key:common::state::off%]", 24 | "on": "[%key:common::state::on%]", 25 | "paused": "[%key:common::state::paused%]", 26 | "returning": "Returning to dock" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ruohonleikkuri", 3 | "device_automation": { 4 | "condition_type": { 5 | "is_docked": "{entity_name} on latausasemassa", 6 | "is_cleaning": "{entity_name} leikkaa ruohoa" 7 | }, 8 | "trigger_type": { 9 | "cleaning": "{entity_name} alkoi leikkaamaan ruohoa", 10 | "docked": "{entity_name} palasi latausasemaan" 11 | }, 12 | "action_type": { 13 | "clean": "Laita {entity_name} leikkaamaan ruohoa", 14 | "dock": "Palauta {entity_name} latausasemaan" 15 | } 16 | }, 17 | "state": { 18 | "_": { 19 | "cleaning": "Leikkaa ruohoa", 20 | "docked": "Latausasemassa", 21 | "error": "Virhe", 22 | "idle": "[%key:common::state::idle%]", 23 | "off": "[%key:common::state::off%]", 24 | "on": "[%key:common::state::on%]", 25 | "paused": "[%key:common::state::paused%]", 26 | "returning": "Palaamassa latausasemaan" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Maaier", 3 | "device_automation": { 4 | "condition_type": { 5 | "is_docked": "{entity_name} is geparkeerd", 6 | "is_cleaning": "{entity_name} is aan het maaien" 7 | }, 8 | "trigger_type": { 9 | "cleaning": "{entity_name} is gestart met maaien", 10 | "docked": "{entity_name} parkeert" 11 | }, 12 | "action_type": { 13 | "clean": "Laat {entity_name} maaien", 14 | "dock": "Laat {entity_name} parkeren" 15 | } 16 | }, 17 | "state": { 18 | "_": { 19 | "cleaning": "Maaien", 20 | "docked": "Geparkeerd", 21 | "error": "Fout", 22 | "idle": "[%key:common::state::idle%]", 23 | "off": "[%key:common::state::off%]", 24 | "on": "[%key:common::state::on%]", 25 | "paused": "[%key:common::state::paused%]", 26 | "returning": "Terug naar laadstation aan het gaan" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Mower", 3 | "device_automation": { 4 | "condition_type": { 5 | "is_docked": "{entity_name} je dokovaný", 6 | "is_cleaning": "{entity_name} kosí" 7 | }, 8 | "trigger_type": { 9 | "cleaning": "{entity_name} začalo kosenie", 10 | "docked": "{entity_name} dokovaný" 11 | }, 12 | "action_type": { 13 | "clean": "Let {entity_name} kosenie", 14 | "dock": "Let {entity_name} návrat do doku" 15 | } 16 | }, 17 | "state": { 18 | "_": { 19 | "cleaning": "Kosenie", 20 | "docked": "Dokovaný", 21 | "error": "Chyba", 22 | "idle": "[%key:common::state::idle%]", 23 | "off": "[%key:common::state::off%]", 24 | "on": "[%key:common::state::on%]", 25 | "paused": "[%key:common::state::paused%]", 26 | "returning": "Návrat do doku" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /custom_components/gardena_smart_system/translations/vacuum.sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Robotgräsklippare", 3 | "device_automation": { 4 | "condition_type": { 5 | "is_docked": "{entity_name} dockad i laddstation", 6 | "is_cleaning": "{entity_name} klipper" 7 | }, 8 | "trigger_type": { 9 | "cleaning": "{entity_name} började klippa", 10 | "docked": "{entity_name} dockad i laddstation" 11 | }, 12 | "action_type": { 13 | "clean": "Låt {entity_name} klippa", 14 | "dock": "Låt {entity_name} återvända till laddstation" 15 | } 16 | }, 17 | "state": { 18 | "_": { 19 | "cleaning": "Klipper", 20 | "docked": "Dockad i laddstation", 21 | "error": "Fel", 22 | "idle": "[%key:common::state::idle%]", 23 | "off": "[%key:common::state::off%]", 24 | "on": "[%key:common::state::on%]", 25 | "paused": "[%key:common::state::paused%]", 26 | "returning": "Återvänder till laddstation" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gardena Smart System", 3 | "version": "1.3.0", 4 | "domains": ["sensor", "switch", "lawn_mower", "binary_sensor"], 5 | "render_readme": true, 6 | "homeassistant": "2023.1.5" 7 | } -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs) 2 | [![hass-gardena-smart-system](https://img.shields.io/github/release/py-smart-gardena/hass-gardena-smart-system.svg?1)](https://github.com/py-smart-gardena/hass-gardena-smart-system) 3 | ![Maintenance](https://img.shields.io/maintenance/yes/2020.svg) 4 | 5 | {% if prerelease %} 6 | ### NB!: This is a Beta version! 7 | {% endif %} 8 | 9 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) 10 | [![hass-gardena-smart-system](https://img.shields.io/github/release/py-smart-gardena/hass-gardena-smart-system.svg?1)](https://github.com/py-smart-gardena/hass-gardena-smart-system) 11 | 12 | Feel free to join the discord server : [![Support Server](https://img.shields.io/discord/853252789522268180.svg?color=7289da&label=Discord&logo=discord&style=flat-square)](https://discord.gg/59sFjykS) 13 | 14 | # Home Assistant integration for Gardena Smart System 15 | 16 | Custom component to support Gardena Smart System devices. 17 | 18 | 19 | 20 | **Table of Contents** 21 | 22 | - [About](#about) 23 | - [Installation](#installation) 24 | - [Installation through HACS](#installation-through-hacs) 25 | - [Manual installation](#manual-installation) 26 | - [Configuration](#configuration) 27 | - [Home Assistant](#home-assistant) 28 | - [Gardena Application Key / Client ID and Application secret / client secret](#gardena-application-key--client-id-and-application-secret--client-secret) 29 | - [Supported devices](#supported-devices) 30 | - [Services](#services) 31 | - [Smart Irrigation Control services](#smart-irrigation-control-services) 32 | - [Smart Mower services](#smart-mower-services) 33 | - [Smart Power Socket services](#smart-power-socket-services) 34 | - [Smart Sensor services](#smart-sensor-services) 35 | - [Smart Water Control services](#smart-water-control-services) 36 | - [Recipes](#recipes) 37 | - [Development](#development) 38 | - [Debugging](#debugging) 39 | - [Changelog](#changelog) 40 | - [1.0.0b5](#100b5) 41 | - [0.2.1](#021) 42 | - [0.2.0](#020) 43 | - [0.1.0](#010) 44 | - [Development](#development-1) 45 | - [Debugging](#debugging-1) 46 | - [TODO](#todo) 47 | 48 | 49 | 50 | > :warning: **Starting from version 1.0.0b5: You might probably have to uninstall and reinstall the integration as credentials requirements and method has changed. THERE IS A BREAKING CHANGE IN THE CONFIGURATION DUE TO AN UPDATE ON THE GARDENA API** 51 | 52 | 53 | ## About 54 | 55 | This component is originally based on 56 | https://github.com/grm/home-assistant/tree/feature/smart_gardena and 57 | https://github.com/grm/py-smart-gardena 58 | 59 | The integration / component has been changed quite a lot, mainly to 60 | add support for config flow setup and Home Assistant devices. It has 61 | also been cleaned up and some bugs have been fixed. Gardena devices 62 | are now represented as Home Assistant devices, which have battery 63 | level sensors where applicable. 64 | 65 | **This project needs your support.** 66 | Gardena equipments are expensive, and I need to buy them in order to add support. 67 | If you find this library useful and want to help me support more devices (or if you 68 | just want to reward me for my spent time), you are very welcome ! 69 | Your help is very much appreciated. 70 | 71 | Here are the links if you want to show your support : 72 | PayPal donate button 73 | 74 | ## Installation 75 | 76 | Requires Home Assistant 0.115.0 or newer. 77 | 78 | ### Installation through HACS 79 | 80 | If you have not yet installed HACS, go get it at https://hacs.xyz/ and walk through the installation and configuration. 81 | 82 | Then find the Gardena Smart System integration in HACS and install it. 83 | 84 | Restart Home Assistant! 85 | 86 | Install the new integration through *Configuration -> Integrations* in HA (see below). 87 | 88 | 89 | ### Manual installation 90 | 91 | Copy the sub-path `/hass-gardena-smart-system/custom_components/gardena_smart_system` of this repo into the path `/config/custom_components/gardena_smart_system` of your HA installation. 92 | 93 | Alternatively use the following commands within an SSH shell into your HA system. 94 | Do NOT try to execute these commands directly your PC on a mounted HA file system. The resulting symlink would be broken for the HA file system. 95 | ``` 96 | cd /config 97 | git clone https://github.com/py-smart-gardena/hass-gardena-smart-system.git 98 | 99 | # if folder custom_components does not yet exist: 100 | mkdir custom_components 101 | 102 | cd custom_components 103 | ln -s ../hass-gardena-smart-system/custom_components/gardena_smart_system 104 | ``` 105 | 106 | ## Configuration 107 | 108 | 109 | ### Home Assistant 110 | 111 | Setup under Integrations in Home Assistant, search for "Gardena Smart 112 | System". You need to enter your application key / client ID and your applications secret / client secret. See below for how to get your Gardena application key and secret. 113 | 114 | After setting up the integration, you can adjust some options on the 115 | integration panel for it. 116 | 117 | Even though this integration can be installed and configured via the 118 | Home Assistant GUI (uses config flow), you might have to restart Home 119 | Assistant to get it working. 120 | 121 | 122 | ### Gardena Application Key / Client ID and Application secret / client secret 123 | 124 | In order to use this integration you must get a client ID / 125 | Application Key from Gardena/Husqvarna. 126 | 127 | 1. Go to https://developer.husqvarnagroup.cloud/ 128 | 129 | 2. Create an account if needed, otherwise sign in with your Gardena 130 | account. 131 | 132 | 3. After signing in you will be automatically redirected to "Your 133 | applications". (Otherwise go to: https://developer.husqvarnagroup.cloud/apps) 134 | 135 | 4. Create an new application, name it for example "My Home Assistant" 136 | (doesn't matter), leave the other fields empty. 137 | 138 | 5. Click on "+Connect new API" and connect the Authentication API and 139 | the GARDENA smart system API. 140 | 141 | 6. Copy your Application Key and Application secret, this is what you need when you add the integration in Home Assistant. 142 | 143 | 144 | ## Supported devices 145 | 146 | The following devices are supported : 147 | 148 | * Gardena Smart Irrigation Control (as switch) 149 | * Gardena Smart Mower (as lawn_mower) 150 | * Gardena Smart Sensor (as sensor) 151 | * Gardena Smart Water Control (as switch) 152 | * Gardena Smart Power Socket (as switch) 153 | 154 | ## Services 155 | 156 | ### Smart Irrigation Control services 157 | 158 | > [TODO: document services] 159 | 160 | ### Smart Mower services 161 | 162 | `lawn_mower.start_mowing` 163 | Start the mower using the Gardena API command START_DONT_OVERRIDE. 164 | The mower resumes the schedule. 165 | 166 | `lawn_mower.pause` 167 | Stop the mower using the Gardena API command PARK_UNTIL_FURTHER_NOTICE. 168 | The mower cancels the current operation, returns to charging station and ignores schedule. 169 | 170 | `lawn_mower.dock` 171 | Stop the mower using Gardena API command PARK_UNTIL_NEXT_TASK. 172 | The mower cancels the current operation and returns to charging station. It will reactivate with the next schedule. 173 | 174 | `gardena_smart_system.start_override` 175 | Start the mower using the Gardena API command START_SECONDS_TO_OVERRIDE. 176 | The mower starts immediately for the duration parameter of the action. 177 | 178 | ### Smart Power Socket services 179 | 180 | > [TODO: document services] 181 | 182 | ### Smart Sensor services 183 | 184 | > [TODO: document services] 185 | 186 | ### Smart Water Control services 187 | 188 | > [TODO: document services] 189 | 190 | ## Recipes 191 | 192 | Some recipes were made by the community. 193 | You can find them [here](RECIPES.md). 194 | 195 | ## Development 196 | 197 | ### Debugging 198 | 199 | To enable debug logging for this integration and related libraries you 200 | can control this in your Home Assistant `configuration.yaml` 201 | file. Example: 202 | 203 | ``` 204 | logger: 205 | default: info 206 | logs: 207 | custom_components.gardena_smart_system: debug 208 | custom_components.gardena_smart_system.mower : debug 209 | custom_components.gardena_smart_system.sensor : debug 210 | custom_components.gardena_smart_system.switch : debug 211 | custom_components.gardena_smart_system.config_flow : debug 212 | 213 | gardena: debug 214 | gardena.smart_system: debug 215 | websocket: debug 216 | ``` 217 | 218 | After a restart detailed log entries will appear in `/config/home-assistant.log`. 219 | 220 | ## Changelog 221 | 222 | ### 1.0.0b5 223 | - Update credentials requirements : the integration need to be completely removed from home assistant and resintalled 224 | 225 | ### 0.2.1 226 | - Correct a bug with some mowers where the wrong id was chosen 227 | - Correct a bug with the water pump where the wrong id was chosen 228 | 229 | ### 0.2.0 230 | - Integration to default repositories of HACS 231 | - Rename vaccum attributes for mower as described : 232 | Two attributes added. Both are compiled from existing attributes: 233 | * `error code` contains NONE if there is no error and the code from `last error code` if an error is active. 234 | * `status code` contains a copy of `activity` as long as there is no error and a copy of `error code` if an error 235 | is active. This corresponds to the state in the old integration of Wijnand. 236 | * Attribute names changed: last_error_code -> last_error, error_code -> error, status_code -> state 237 | * Const ATTR_LAST_ERROR_CODE removed, not used any more. 238 | 239 | ### 0.1.0 240 | - First release with a version number 241 | - Bump py-smart-gardena to 0.7.4 242 | - Connection stability has been improved from updating py-smart-gardena 243 | - A binary_sensor has been added which holds the websocket connection status (its status goes offline a few seconds from time to time when the access token expires while the access token is refreshed) 244 | 245 | ## Development 246 | 247 | ### Debugging 248 | 249 | To enable debug logging for this integration and related libraries you 250 | can control this in your Home Assistant `configuration.yaml` 251 | file. Example: 252 | 253 | ``` 254 | logger: 255 | default: info 256 | logs: 257 | custom_components.gardena_smart_system: debug 258 | custom_components.gardena_smart_system.mower : debug 259 | custom_components.gardena_smart_system.sensor : debug 260 | custom_components.gardena_smart_system.switch : debug 261 | custom_components.gardena_smart_system.config_flow : debug 262 | 263 | gardena: debug 264 | gardena.smart_system: debug 265 | websocket: debug 266 | ``` 267 | 268 | After a restart detailed log entries will appear in `/config/home-assistant.log`. 269 | 270 | ### TODO 271 | 272 | * Do we need support for more than one location? Should we make it 273 | possible to configure it? 274 | --------------------------------------------------------------------------------