├── .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 | [](https://github.com/custom-components/hacs)
2 | [](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 |
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 | 
227 |
228 | 
229 | 
230 | 
231 | 
232 | 
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 | 
237 | 
238 | 
239 | 
240 | 
241 | 
242 | 
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 | [](https://github.com/custom-components/hacs)
2 | [](https://github.com/py-smart-gardena/hass-gardena-smart-system)
3 | 
4 |
5 | {% if prerelease %}
6 | ### NB!: This is a Beta version!
7 | {% endif %}
8 |
9 | [](https://github.com/custom-components/hacs)
10 | [](https://github.com/py-smart-gardena/hass-gardena-smart-system)
11 |
12 | Feel free to join the discord server : [](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 |
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 |
--------------------------------------------------------------------------------