0.105.0
This commit is contained in:
Franck Nijhof 2020-02-05 20:02:36 +01:00 committed by GitHub
commit b2cd6707b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1011 changed files with 28062 additions and 13387 deletions

View File

@ -6,7 +6,6 @@ omit =
homeassistant/helpers/signal.py homeassistant/helpers/signal.py
homeassistant/helpers/typing.py homeassistant/helpers/typing.py
homeassistant/scripts/*.py homeassistant/scripts/*.py
homeassistant/util/async.py
# omit pieces of code that rely on external devices being present # omit pieces of code that rely on external devices being present
homeassistant/components/abode/__init__.py homeassistant/components/abode/__init__.py
@ -32,7 +31,6 @@ omit =
homeassistant/components/airly/const.py homeassistant/components/airly/const.py
homeassistant/components/airvisual/sensor.py homeassistant/components/airvisual/sensor.py
homeassistant/components/aladdin_connect/cover.py homeassistant/components/aladdin_connect/cover.py
homeassistant/components/alarm_control_panel/manual_mqtt.py
homeassistant/components/alarmdecoder/* homeassistant/components/alarmdecoder/*
homeassistant/components/alarmdotcom/alarm_control_panel.py homeassistant/components/alarmdotcom/alarm_control_panel.py
homeassistant/components/alpha_vantage/sensor.py homeassistant/components/alpha_vantage/sensor.py
@ -116,7 +114,6 @@ omit =
homeassistant/components/cisco_ios/device_tracker.py homeassistant/components/cisco_ios/device_tracker.py
homeassistant/components/cisco_mobility_express/device_tracker.py homeassistant/components/cisco_mobility_express/device_tracker.py
homeassistant/components/cisco_webex_teams/notify.py homeassistant/components/cisco_webex_teams/notify.py
homeassistant/components/ciscospark/notify.py
homeassistant/components/citybikes/sensor.py homeassistant/components/citybikes/sensor.py
homeassistant/components/clementine/media_player.py homeassistant/components/clementine/media_player.py
homeassistant/components/clickatell/notify.py homeassistant/components/clickatell/notify.py
@ -256,13 +253,15 @@ omit =
homeassistant/components/frontier_silicon/media_player.py homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py homeassistant/components/garadget/cover.py
homeassistant/components/garmin_connect/__init__.py
homeassistant/components/garmin_connect/const.py
homeassistant/components/garmin_connect/sensor.py
homeassistant/components/gc100/* homeassistant/components/gc100/*
homeassistant/components/geniushub/* homeassistant/components/geniushub/*
homeassistant/components/gearbest/sensor.py homeassistant/components/gearbest/sensor.py
homeassistant/components/geizhals/sensor.py homeassistant/components/geizhals/sensor.py
homeassistant/components/gios/__init__.py homeassistant/components/gios/__init__.py
homeassistant/components/gios/air_quality.py homeassistant/components/gios/air_quality.py
homeassistant/components/gios/consts.py
homeassistant/components/github/sensor.py homeassistant/components/github/sensor.py
homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitlab_ci/sensor.py
homeassistant/components/gitter/sensor.py homeassistant/components/gitter/sensor.py
@ -307,7 +306,6 @@ omit =
homeassistant/components/homematic/notify.py homeassistant/components/homematic/notify.py
homeassistant/components/homeworks/* homeassistant/components/homeworks/*
homeassistant/components/honeywell/climate.py homeassistant/components/honeywell/climate.py
homeassistant/components/hook/switch.py
homeassistant/components/horizon/media_player.py homeassistant/components/horizon/media_player.py
homeassistant/components/hp_ilo/sensor.py homeassistant/components/hp_ilo/sensor.py
homeassistant/components/htu21d/sensor.py homeassistant/components/htu21d/sensor.py
@ -324,6 +322,7 @@ omit =
homeassistant/components/iaqualink/sensor.py homeassistant/components/iaqualink/sensor.py
homeassistant/components/iaqualink/switch.py homeassistant/components/iaqualink/switch.py
homeassistant/components/icloud/__init__.py homeassistant/components/icloud/__init__.py
homeassistant/components/icloud/account.py
homeassistant/components/icloud/device_tracker.py homeassistant/components/icloud/device_tracker.py
homeassistant/components/icloud/sensor.py homeassistant/components/icloud/sensor.py
homeassistant/components/izone/climate.py homeassistant/components/izone/climate.py
@ -421,7 +420,8 @@ omit =
homeassistant/components/metoffice/weather.py homeassistant/components/metoffice/weather.py
homeassistant/components/microsoft/tts.py homeassistant/components/microsoft/tts.py
homeassistant/components/miflora/sensor.py homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/* homeassistant/components/mikrotik/hub.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/climate.py homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py homeassistant/components/mill/const.py
homeassistant/components/minio/* homeassistant/components/minio/*
@ -455,8 +455,13 @@ omit =
homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nederlandse_spoorwegen/sensor.py
homeassistant/components/nello/lock.py homeassistant/components/nello/lock.py
homeassistant/components/nest/* homeassistant/components/nest/*
homeassistant/components/netatmo/* homeassistant/components/netatmo/__init__.py
homeassistant/components/netatmo_public/sensor.py homeassistant/components/netatmo/binary_sensor.py
homeassistant/components/netatmo/api.py
homeassistant/components/netatmo/camera.py
homeassistant/components/netatmo/climate.py
homeassistant/components/netatmo/const.py
homeassistant/components/netatmo/sensor.py
homeassistant/components/netdata/sensor.py homeassistant/components/netdata/sensor.py
homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear/device_tracker.py
homeassistant/components/netgear_lte/* homeassistant/components/netgear_lte/*
@ -504,13 +509,13 @@ omit =
homeassistant/components/openuv/sensor.py homeassistant/components/openuv/sensor.py
homeassistant/components/openweathermap/sensor.py homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather.py homeassistant/components/openweathermap/weather.py
homeassistant/components/opnsense/*
homeassistant/components/opple/light.py homeassistant/components/opple/light.py
homeassistant/components/orangepi_gpio/* homeassistant/components/orangepi_gpio/*
homeassistant/components/oru/* homeassistant/components/oru/*
homeassistant/components/orvibo/switch.py homeassistant/components/orvibo/switch.py
homeassistant/components/osramlightify/light.py homeassistant/components/osramlightify/light.py
homeassistant/components/otp/sensor.py homeassistant/components/otp/sensor.py
homeassistant/components/owlet/*
homeassistant/components/panasonic_bluray/media_player.py homeassistant/components/panasonic_bluray/media_player.py
homeassistant/components/panasonic_viera/media_player.py homeassistant/components/panasonic_viera/media_player.py
homeassistant/components/pandora/media_player.py homeassistant/components/pandora/media_player.py
@ -530,12 +535,10 @@ omit =
homeassistant/components/plex/media_player.py homeassistant/components/plex/media_player.py
homeassistant/components/plex/sensor.py homeassistant/components/plex/sensor.py
homeassistant/components/plex/server.py homeassistant/components/plex/server.py
homeassistant/components/plex/websockets.py
homeassistant/components/plugwise/* homeassistant/components/plugwise/*
homeassistant/components/plum_lightpad/* homeassistant/components/plum_lightpad/*
homeassistant/components/pocketcasts/sensor.py homeassistant/components/pocketcasts/sensor.py
homeassistant/components/point/* homeassistant/components/point/*
homeassistant/components/postnl/sensor.py
homeassistant/components/prezzibenzina/sensor.py homeassistant/components/prezzibenzina/sensor.py
homeassistant/components/proliphix/climate.py homeassistant/components/proliphix/climate.py
homeassistant/components/prometheus/* homeassistant/components/prometheus/*
@ -637,6 +640,7 @@ omit =
homeassistant/components/smappee/* homeassistant/components/smappee/*
homeassistant/components/smarty/* homeassistant/components/smarty/*
homeassistant/components/smarthab/* homeassistant/components/smarthab/*
homeassistant/components/sms/*
homeassistant/components/smtp/notify.py homeassistant/components/smtp/notify.py
homeassistant/components/snapcast/media_player.py homeassistant/components/snapcast/media_player.py
homeassistant/components/snmp/* homeassistant/components/snmp/*
@ -659,6 +663,7 @@ omit =
homeassistant/components/speedtestdotnet/* homeassistant/components/speedtestdotnet/*
homeassistant/components/spider/* homeassistant/components/spider/*
homeassistant/components/spotcrime/sensor.py homeassistant/components/spotcrime/sensor.py
homeassistant/components/spotify/__init__.py
homeassistant/components/spotify/media_player.py homeassistant/components/spotify/media_player.py
homeassistant/components/squeezebox/* homeassistant/components/squeezebox/*
homeassistant/components/starline/* homeassistant/components/starline/*
@ -724,7 +729,6 @@ omit =
homeassistant/components/torque/sensor.py homeassistant/components/torque/sensor.py
homeassistant/components/totalconnect/* homeassistant/components/totalconnect/*
homeassistant/components/touchline/climate.py homeassistant/components/touchline/climate.py
homeassistant/components/tplink/device_tracker.py
homeassistant/components/tplink/switch.py homeassistant/components/tplink/switch.py
homeassistant/components/tplink_lte/* homeassistant/components/tplink_lte/*
homeassistant/components/traccar/device_tracker.py homeassistant/components/traccar/device_tracker.py
@ -749,7 +753,6 @@ omit =
homeassistant/components/twitch/sensor.py homeassistant/components/twitch/sensor.py
homeassistant/components/twitter/notify.py homeassistant/components/twitter/notify.py
homeassistant/components/ubee/device_tracker.py homeassistant/components/ubee/device_tracker.py
homeassistant/components/uber/sensor.py
homeassistant/components/ubus/device_tracker.py homeassistant/components/ubus/device_tracker.py
homeassistant/components/ue_smart_radio/media_player.py homeassistant/components/ue_smart_radio/media_player.py
homeassistant/components/unifiled/* homeassistant/components/unifiled/*
@ -779,7 +782,9 @@ omit =
homeassistant/components/viaggiatreno/sensor.py homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/* homeassistant/components/vicare/*
homeassistant/components/vivotek/camera.py homeassistant/components/vivotek/camera.py
homeassistant/components/vizio/* homeassistant/components/vizio/__init__.py
homeassistant/components/vizio/const.py
homeassistant/components/vizio/media_player.py
homeassistant/components/vlc/media_player.py homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/media_player.py homeassistant/components/vlc_telnet/media_player.py
homeassistant/components/volkszaehler/sensor.py homeassistant/components/volkszaehler/sensor.py
@ -825,7 +830,6 @@ omit =
homeassistant/components/zestimate/sensor.py homeassistant/components/zestimate/sensor.py
homeassistant/components/zha/__init__.py homeassistant/components/zha/__init__.py
homeassistant/components/zha/api.py homeassistant/components/zha/api.py
homeassistant/components/zha/const.py
homeassistant/components/zha/core/channels/* homeassistant/components/zha/core/channels/*
homeassistant/components/zha/core/const.py homeassistant/components/zha/core/const.py
homeassistant/components/zha/core/device.py homeassistant/components/zha/core/device.py
@ -833,7 +837,6 @@ omit =
homeassistant/components/zha/core/helpers.py homeassistant/components/zha/core/helpers.py
homeassistant/components/zha/core/patches.py homeassistant/components/zha/core/patches.py
homeassistant/components/zha/core/registries.py homeassistant/components/zha/core/registries.py
homeassistant/components/zha/device_entity.py
homeassistant/components/zha/entity.py homeassistant/components/zha/entity.py
homeassistant/components/zha/light.py homeassistant/components/zha/light.py
homeassistant/components/zha/sensor.py homeassistant/components/zha/sensor.py

View File

@ -1,48 +0,0 @@
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
- Android issues should be submitted to the home-assistant-android repository: https://github.com/home-assistant/home-assistant-android/issues
- Do not report issues for integrations if you are using custom integration: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
-->
**Home Assistant release with the issue:**
<!--
- Frontend -> Developer tools -> Info
- Or use this command: hass --version
-->
**Last working Home Assistant release (if known):**
**Operating environment (Hass.io/Docker/Windows/etc.):**
<!--
Please provide details about your environment.
-->
**Integration:**
<!--
Please add the link to the documentation at https://www.home-assistant.io/integrations/ of the integration in question.
-->
**Description of problem:**
**Problem-relevant `configuration.yaml` entries and (fill out even if it seems unimportant):**
```yaml
```
**Traceback (if applicable):**
```
```
**Additional information:**

53
.github/ISSUE_TEMPLATE/BUG_REPORT.md vendored Normal file
View File

@ -0,0 +1,53 @@
---
name: Report a bug with Home Assistant
about: Report an issue with Home Assistant
---
<!-- READ THIS FIRST:
- If you need additional help with this template, please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Do not report issues for integrations if you are using custom components or integrations.
- Provide as many details as possible. Paste logs, configuration samples and code into the backticks.
DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed without comment.
-->
## The problem
<!--
Describe the issue you are experiencing here to communicate to the
maintainers. Tell us what you were trying to do and what happened instead.
-->
## Environment
<!--
Provide details about the versions you are using, which helps us to reproduce
and find the issue quicker. Version information is found in the
Home Assistant frontend: Developer tools -> Info.
-->
- Home Assistant release with the issue:
- Last working Home Assistant release (if known):
- Operating environment (Hass.io/Docker/Windows/etc.):
- Integration causing this issue:
- Link to integration documentation on our website:
## Problem-relevant `configuration.yaml`
<!--
An example configuration that caused the problem for you. Fill this out even
if it seems unimportant to you. Please be sure to remove personal information
like passwords, private URLs and other credentials.
-->
```yaml
```
## Traceback/Error logs
<!--
If you come across any trace or error logs, please provide them.
-->
```txt
```
## Additional information

View File

@ -1,52 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
- Do not report issues for integrations if you are using a custom integration: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
-->
**Home Assistant release with the issue:**
<!--
- Frontend -> Developer tools -> Info
- Or use this command: hass --version
-->
**Last working Home Assistant release (if known):**
**Operating environment (Hass.io/Docker/Windows/etc.):**
<!--
Please provide details about your environment.
-->
**Integration:**
<!--
Please add the link to the documentation at https://www.home-assistant.io/integrations/ of the integration in question.
-->
**Description of problem:**
**Problem-relevant `configuration.yaml` entries and (fill out even if it seems unimportant):**
```yaml
```
**Traceback (if applicable):**
```
```
**Additional information:**

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: Report a bug with the UI, Frontend or Lovelace
url: https://github.com/home-assistant/home-assistant-polymer/issues
about: This is the issue tracker for our backend. Please report issues with the UI in the frontend repository.
- name: Report incorrect or missing information on our website
url: https://github.com/home-assistant/home-assistant.io/issues
about: Our documentation has its own issue tracker. Please report issues with the website there.
- name: I have a question or need support
url: https://www.home-assistant.io/help
about: We use GitHub for tracking bugs, check our website for resources on getting help.
- name: Feature Request
url: https://community.home-assistant.io/c/feature-requests
about: Please use our Community Forum for making feature requests.
- name: I'm unsure where to go
url: https://www.home-assistant.io/join-chat
about: If you are unsure where to go, then joining our chat is recommended; Just ask!

View File

@ -1,35 +1,109 @@
## Breaking Change: <!--
You are amazing! Thanks for contributing to our project!
<!-- What is breaking and why we have to break it. Remove this section only if it was NOT a breaking change. --> Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Description: ## Breaking change
<!--
If your PR contains a breaking change for existing users, it is important
to tell them what breaks, how to make it work again and why we did this.
This piece of text is published with the release notes, so it helps if you
write it towards our users, not us.
Note: Remove this section if this PR is NOT a breaking change.
-->
**Related issue (if applicable):** fixes #<home-assistant issue number goes here> ## Proposed change
<!--
Describe the big picture of your changes here to communicate to the
maintainers why we should accept this pull request. If it fixes a bug
or resolves a feature request, be sure to link to that issue in the
additional information section.
-->
**Pull request with documentation for [home-assistant.io](https://github.com/home-assistant/home-assistant.io) (if applicable):** home-assistant/home-assistant.io#<home-assistant.io PR number goes here>
## Example entry for `configuration.yaml` (if applicable): ## Type of change
<!--
What type of change does your PR introduce to Home Assistant?
NOTE: Please, check only 1! box!
If your PR requires multiple boxes to be checked, you'll most likely need to
split it into multiple PRs. This makes things easier and faster to code review.
-->
- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
## Example entry for `configuration.yaml`:
<!--
Supplying a configuration snippet, makes it easier for a maintainer to test
your PR. Furthermore, for new integrations, it gives an impression of how
the configuration would look like.
Note: Remove this section if this PR does not have an example entry.
-->
```yaml ```yaml
# Example configuration.yaml
``` ```
## Checklist: ## Additional information
- [ ] The code change is tested and works locally. <!--
- [ ] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass** Details are important, and help maintainers processing your PR.
- [ ] There is no commented out code in this PR. Please be sure to fill out additional details, if applicable.
- [ ] I have followed the [development checklist][dev-checklist] -->
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
- Link to documentation pull request:
## Checklist
<!--
Put an `x` in the boxes that apply. You can also fill these out after
creating the PR. If you're unsure about any of them, don't hesitate to ask.
We're here to help! This is simply a reminder of what we are going to look
for before merging your code.
-->
- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] The code has been formatted using Black (`black --fast homeassistant tests`)
- [ ] Tests have been added to verify that the new code works.
If user exposed functionality or configuration variables are added/changed: If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
If the code communicates with devices, web services, or third-party tools: If the code communicates with devices, web services, or third-party tools:
- [ ] [_The manifest file_][manifest-docs] has all fields filled out correctly. Update and include derived files by running `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt` by running `python3 -m script.gen_requirements_all`.
- [ ] Untested files have been added to `.coveragerc`.
If the code does not interact with devices: - [ ] The [manifest file][manifest-docs] has all fields filled out correctly.
- [ ] Tests have been added to verify that the new code works. Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`.
Updated by running `python3 -m script.gen_requirements_all`.
- [ ] Untested files have been added to `.coveragerc`.
The integration reached or maintains the following [Integration Quality Scale][quality-scale]:
<!--
The Integration Quality Scale scores an integration on the code quality
and user experience. Each level of the quality scale consists of a list
of requirements. We highly recommend getting your integration scored!
-->
- [ ] No score or internal
- [ ] 🥈 Silver
- [ ] 🥇 Gold
- [ ] 🏆 Platinum
<!--
Thank you for contributing <3
Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/en/development_checklist.html [dev-checklist]: https://developers.home-assistant.io/docs/en/development_checklist.html
[manifest-docs]: https://developers.home-assistant.io/docs/en/creating_integration_manifest.html [manifest-docs]: https://developers.home-assistant.io/docs/en/creating_integration_manifest.html
[quality-scale]: https://developers.home-assistant.io/docs/en/next/integration_quality_scale_index.html
[docs-repository]: https://github.com/home-assistant/home-assistant.io

View File

@ -1,2 +0,0 @@
python:
enabled: true

View File

@ -24,7 +24,7 @@ repos:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
- flake8-docstrings==1.5.0 - flake8-docstrings==1.5.0
- pydocstyle==5.0.1 - pydocstyle==5.0.2
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.6.2 rev: 1.6.2

View File

@ -20,7 +20,7 @@ repos:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
- flake8-docstrings==1.5.0 - flake8-docstrings==1.5.0
- pydocstyle==5.0.1 - pydocstyle==5.0.2
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.6.2 rev: 1.6.2

View File

@ -23,6 +23,7 @@ homeassistant/components/alpha_vantage/* @fabaff
homeassistant/components/amazon_polly/* @robbiet480 homeassistant/components/amazon_polly/* @robbiet480
homeassistant/components/ambiclimate/* @danielhiversen homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya homeassistant/components/ambient_station/* @bachya
homeassistant/components/amcrest/* @pnbruckner
homeassistant/components/androidtv/* @JeffLIrion homeassistant/components/androidtv/* @JeffLIrion
homeassistant/components/apache_kafka/* @bachya homeassistant/components/apache_kafka/* @bachya
homeassistant/components/api/* @home-assistant/core homeassistant/components/api/* @home-assistant/core
@ -59,7 +60,6 @@ homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren
homeassistant/components/cisco_ios/* @fbradyirl homeassistant/components/cisco_ios/* @fbradyirl
homeassistant/components/cisco_mobility_express/* @fbradyirl homeassistant/components/cisco_mobility_express/* @fbradyirl
homeassistant/components/cisco_webex_teams/* @fbradyirl homeassistant/components/cisco_webex_teams/* @fbradyirl
homeassistant/components/ciscospark/* @fbradyirl
homeassistant/components/cloud/* @home-assistant/cloud homeassistant/components/cloud/* @home-assistant/cloud
homeassistant/components/cloudflare/* @ludeeus homeassistant/components/cloudflare/* @ludeeus
homeassistant/components/comfoconnect/* @michaelarnauts homeassistant/components/comfoconnect/* @michaelarnauts
@ -76,12 +76,14 @@ homeassistant/components/darksky/* @fabaff
homeassistant/components/deconz/* @kane610 homeassistant/components/deconz/* @kane610
homeassistant/components/delijn/* @bollewolle homeassistant/components/delijn/* @bollewolle
homeassistant/components/demo/* @home-assistant/core homeassistant/components/demo/* @home-assistant/core
homeassistant/components/derivative/* @afaucogney
homeassistant/components/device_automation/* @home-assistant/core homeassistant/components/device_automation/* @home-assistant/core
homeassistant/components/digital_ocean/* @fabaff homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/discogs/* @thibmaek homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7 homeassistant/components/doorbird/* @oblogic7
homeassistant/components/dsmr_reader/* @depl0y homeassistant/components/dsmr_reader/* @depl0y
homeassistant/components/dweet/* @fabaff homeassistant/components/dweet/* @fabaff
homeassistant/components/dyson/* @etheralm
homeassistant/components/ecobee/* @marthoc homeassistant/components/ecobee/* @marthoc
homeassistant/components/ecovacs/* @OverloadUT homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/egardia/* @jeroenterheerdt homeassistant/components/egardia/* @jeroenterheerdt
@ -89,7 +91,6 @@ homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/elgato/* @frenck homeassistant/components/elgato/* @frenck
homeassistant/components/elv/* @majuss homeassistant/components/elv/* @majuss
homeassistant/components/emby/* @mezz64 homeassistant/components/emby/* @mezz64
homeassistant/components/emulated_hue/* @NobleKangaroo
homeassistant/components/enigma2/* @fbradyirl homeassistant/components/enigma2/* @fbradyirl
homeassistant/components/enocean/* @bdurrer homeassistant/components/enocean/* @bdurrer
homeassistant/components/entur_public_transport/* @hfurubotten homeassistant/components/entur_public_transport/* @hfurubotten
@ -115,6 +116,7 @@ homeassistant/components/foursquare/* @robbiet480
homeassistant/components/freebox/* @snoof85 homeassistant/components/freebox/* @snoof85
homeassistant/components/fronius/* @nielstron homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/garmin_connect/* @cyberjunky
homeassistant/components/gearbest/* @HerrHofrat homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geo_rss_events/* @exxamalte homeassistant/components/geo_rss_events/* @exxamalte
@ -129,6 +131,7 @@ homeassistant/components/google_cloud/* @lufton
homeassistant/components/google_translate/* @awarecan homeassistant/components/google_translate/* @awarecan
homeassistant/components/google_travel_time/* @robbiet480 homeassistant/components/google_travel_time/* @robbiet480
homeassistant/components/gpsd/* @fabaff homeassistant/components/gpsd/* @fabaff
homeassistant/components/greeneye_monitor/* @jkeljo
homeassistant/components/group/* @home-assistant/core homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning homeassistant/components/growatt_server/* @indykoning
homeassistant/components/gtfs/* @robbiet480 homeassistant/components/gtfs/* @robbiet480
@ -168,7 +171,7 @@ homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo homeassistant/components/intesishome/* @jnimmo
homeassistant/components/ios/* @robbiet480 homeassistant/components/ios/* @robbiet480
homeassistant/components/iperf3/* @rohankapoorcom homeassistant/components/iperf3/* @rohankapoorcom
homeassistant/components/ipma/* @dgomes homeassistant/components/ipma/* @dgomes @abmantis
homeassistant/components/iqvia/* @bachya homeassistant/components/iqvia/* @bachya
homeassistant/components/irish_rail_transport/* @ttroy50 homeassistant/components/irish_rail_transport/* @ttroy50
homeassistant/components/izone/* @Swamp-Ig homeassistant/components/izone/* @Swamp-Ig
@ -206,6 +209,7 @@ homeassistant/components/met/* @danielhiversen
homeassistant/components/meteo_france/* @victorcerutti @oncleben31 homeassistant/components/meteo_france/* @victorcerutti @oncleben31
homeassistant/components/meteoalarm/* @rolfberkenbosch homeassistant/components/meteoalarm/* @rolfberkenbosch
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
homeassistant/components/mikrotik/* @engrbm87
homeassistant/components/mill/* @danielhiversen homeassistant/components/mill/* @danielhiversen
homeassistant/components/min_max/* @fabaff homeassistant/components/min_max/* @fabaff
homeassistant/components/minio/* @tkislan homeassistant/components/minio/* @tkislan
@ -219,9 +223,11 @@ homeassistant/components/msteams/* @peroyvind
homeassistant/components/mysensors/* @MartinHjelmare homeassistant/components/mysensors/* @MartinHjelmare
homeassistant/components/mystrom/* @fabaff homeassistant/components/mystrom/* @fabaff
homeassistant/components/neato/* @dshokouhi @Santobert homeassistant/components/neato/* @dshokouhi @Santobert
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
homeassistant/components/nello/* @pschmitt homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444 homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @awarecan homeassistant/components/nest/* @awarecan
homeassistant/components/netatmo/* @cgtobi
homeassistant/components/netdata/* @fabaff homeassistant/components/netdata/* @fabaff
homeassistant/components/nextbus/* @vividboarder homeassistant/components/nextbus/* @vividboarder
homeassistant/components/nilu/* @hfurubotten homeassistant/components/nilu/* @hfurubotten
@ -243,9 +249,9 @@ homeassistant/components/onewire/* @garbled1
homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/opentherm_gw/* @mvn23
homeassistant/components/openuv/* @bachya homeassistant/components/openuv/* @bachya
homeassistant/components/openweathermap/* @fabaff homeassistant/components/openweathermap/* @fabaff
homeassistant/components/opnsense/* @mtreinish
homeassistant/components/orangepi_gpio/* @pascallj homeassistant/components/orangepi_gpio/* @pascallj
homeassistant/components/oru/* @bvlaicu homeassistant/components/oru/* @bvlaicu
homeassistant/components/owlet/* @oblogic7
homeassistant/components/panel_custom/* @home-assistant/frontend homeassistant/components/panel_custom/* @home-assistant/frontend
homeassistant/components/panel_iframe/* @home-assistant/frontend homeassistant/components/panel_iframe/* @home-assistant/frontend
homeassistant/components/pcal9535a/* @Shulyaka homeassistant/components/pcal9535a/* @Shulyaka
@ -277,11 +283,13 @@ homeassistant/components/rfxtrx/* @danielhiversen
homeassistant/components/ring/* @balloob homeassistant/components/ring/* @balloob
homeassistant/components/rmvtransport/* @cgtobi homeassistant/components/rmvtransport/* @cgtobi
homeassistant/components/roomba/* @pschmitt homeassistant/components/roomba/* @pschmitt
homeassistant/components/safe_mode/* @home-assistant/core
homeassistant/components/saj/* @fredericvl homeassistant/components/saj/* @fredericvl
homeassistant/components/samsungtv/* @escoand homeassistant/components/samsungtv/* @escoand
homeassistant/components/scene/* @home-assistant/core homeassistant/components/scene/* @home-assistant/core
homeassistant/components/scrape/* @fabaff homeassistant/components/scrape/* @fabaff
homeassistant/components/script/* @home-assistant/core homeassistant/components/script/* @home-assistant/core
homeassistant/components/search/* @home-assistant/core
homeassistant/components/sense/* @kbickar homeassistant/components/sense/* @kbickar
homeassistant/components/sensibo/* @andrey-git homeassistant/components/sensibo/* @andrey-git
homeassistant/components/sentry/* @dcramer homeassistant/components/sentry/* @dcramer
@ -290,14 +298,17 @@ homeassistant/components/seventeentrack/* @bachya
homeassistant/components/shell_command/* @home-assistant/core homeassistant/components/shell_command/* @home-assistant/core
homeassistant/components/shiftr/* @fabaff homeassistant/components/shiftr/* @fabaff
homeassistant/components/shodan/* @fabaff homeassistant/components/shodan/* @fabaff
homeassistant/components/sighthound/* @robmarkcole
homeassistant/components/signal_messenger/* @bbernhard homeassistant/components/signal_messenger/* @bbernhard
homeassistant/components/simplisafe/* @bachya homeassistant/components/simplisafe/* @bachya
homeassistant/components/sinch/* @bendikrb homeassistant/components/sinch/* @bendikrb
homeassistant/components/sisyphus/* @jkeljo
homeassistant/components/slide/* @ualex73 homeassistant/components/slide/* @ualex73
homeassistant/components/sma/* @kellerza homeassistant/components/sma/* @kellerza
homeassistant/components/smarthab/* @outadoc homeassistant/components/smarthab/* @outadoc
homeassistant/components/smartthings/* @andrewsayre homeassistant/components/smartthings/* @andrewsayre
homeassistant/components/smarty/* @z0mbieprocess homeassistant/components/smarty/* @z0mbieprocess
homeassistant/components/sms/* @ocalvo
homeassistant/components/smtp/* @fabaff homeassistant/components/smtp/* @fabaff
homeassistant/components/solaredge_local/* @drobtravels @scheric homeassistant/components/solaredge_local/* @drobtravels @scheric
homeassistant/components/solarlog/* @Ernst79 homeassistant/components/solarlog/* @Ernst79
@ -308,6 +319,7 @@ homeassistant/components/songpal/* @rytilahti
homeassistant/components/spaceapi/* @fabaff homeassistant/components/spaceapi/* @fabaff
homeassistant/components/speedtestdotnet/* @rohankapoorcom homeassistant/components/speedtestdotnet/* @rohankapoorcom
homeassistant/components/spider/* @peternijssen homeassistant/components/spider/* @peternijssen
homeassistant/components/spotify/* @frenck
homeassistant/components/sql/* @dgomes homeassistant/components/sql/* @dgomes
homeassistant/components/starline/* @anonym-tsk homeassistant/components/starline/* @anonym-tsk
homeassistant/components/statistics/* @fabaff homeassistant/components/statistics/* @fabaff
@ -351,6 +363,7 @@ homeassistant/components/tts/* @robbiet480
homeassistant/components/twentemilieu/* @frenck homeassistant/components/twentemilieu/* @frenck
homeassistant/components/twilio_call/* @robbiet480 homeassistant/components/twilio_call/* @robbiet480
homeassistant/components/twilio_sms/* @robbiet480 homeassistant/components/twilio_sms/* @robbiet480
homeassistant/components/ubee/* @mzdrale
homeassistant/components/unifi/* @kane610 homeassistant/components/unifi/* @kane610
homeassistant/components/unifiled/* @florisvdk homeassistant/components/unifiled/* @florisvdk
homeassistant/components/upc_connect/* @pvizeli homeassistant/components/upc_connect/* @pvizeli
@ -360,7 +373,7 @@ homeassistant/components/upnp/* @robbiet480
homeassistant/components/uptimerobot/* @ludeeus homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usgs_earthquakes_feed/* @exxamalte homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @cereal2nd homeassistant/components/velbus/* @Cereal2nd @brefra
homeassistant/components/velux/* @Julius2342 homeassistant/components/velux/* @Julius2342
homeassistant/components/versasense/* @flamm3blemuff1n homeassistant/components/versasense/* @flamm3blemuff1n
homeassistant/components/version/* @fabaff homeassistant/components/version/* @fabaff

View File

@ -30,7 +30,7 @@ jobs:
- template: templates/azp-job-wheels.yaml@azure - template: templates/azp-job-wheels.yaml@azure
parameters: parameters:
builderVersion: '$(versionWheels)' builderVersion: '$(versionWheels)'
builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev' builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev'
builderPip: 'Cython;numpy' builderPip: 'Cython;numpy'
wheelsRequirement: 'requirements_wheels.txt' wheelsRequirement: 'requirements_wheels.txt'
wheelsRequirementDiff: 'requirements_diff.txt' wheelsRequirementDiff: 'requirements_diff.txt'
@ -68,6 +68,7 @@ jobs:
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file} sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
sed -i "s|# py_noaa|py_noaa|g" ${requirement_file} sed -i "s|# py_noaa|py_noaa|g" ${requirement_file}
sed -i "s|# bme680|bme680|g" ${requirement_file} sed -i "s|# bme680|bme680|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
if [[ "$(buildArch)" =~ arm ]]; then if [[ "$(buildArch)" =~ arm ]]; then
sed -i "s|# VL53L1X|VL53L1X|g" ${requirement_file} sed -i "s|# VL53L1X|VL53L1X|g" ${requirement_file}

View File

@ -6,13 +6,10 @@ import platform
import subprocess import subprocess
import sys import sys
import threading import threading
from typing import TYPE_CHECKING, Any, Dict, List from typing import List
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
if TYPE_CHECKING:
from homeassistant import core
def set_loop() -> None: def set_loop() -> None:
"""Attempt to use different loop.""" """Attempt to use different loop."""
@ -78,19 +75,6 @@ def ensure_config_path(config_dir: str) -> None:
sys.exit(1) sys.exit(1)
async def ensure_config_file(hass: "core.HomeAssistant", config_dir: str) -> str:
"""Ensure configuration file exists."""
import homeassistant.config as config_util
config_path = await config_util.async_ensure_config_exists(hass, config_dir)
if config_path is None:
print("Error getting configuration path")
sys.exit(1)
return config_path
def get_arguments() -> argparse.Namespace: def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments.""" """Get parsed passed in arguments."""
import homeassistant.config as config_util import homeassistant.config as config_util
@ -107,7 +91,7 @@ def get_arguments() -> argparse.Namespace:
help="Directory that contains the Home Assistant configuration", help="Directory that contains the Home Assistant configuration",
) )
parser.add_argument( parser.add_argument(
"--demo-mode", action="store_true", help="Start Home Assistant in demo mode" "--safe-mode", action="store_true", help="Start Home Assistant in safe mode"
) )
parser.add_argument( parser.add_argument(
"--debug", action="store_true", help="Start Home Assistant in debug mode" "--debug", action="store_true", help="Start Home Assistant in debug mode"
@ -253,35 +237,21 @@ def cmdline() -> List[str]:
async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int: async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
"""Set up Home Assistant and run.""" """Set up Home Assistant and run."""
from homeassistant import bootstrap, core from homeassistant import bootstrap
hass = core.HomeAssistant() hass = await bootstrap.async_setup_hass(
if args.demo_mode:
config: Dict[str, Any] = {"frontend": {}, "demo": {}}
bootstrap.async_from_config_dict(
config,
hass,
config_dir=config_dir, config_dir=config_dir,
verbose=args.verbose, verbose=args.verbose,
skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days, log_rotate_days=args.log_rotate_days,
log_file=args.log_file, log_file=args.log_file,
log_no_color=args.log_no_color, log_no_color=args.log_no_color,
)
else:
config_file = await ensure_config_file(hass, config_dir)
print("Config directory:", config_dir)
await bootstrap.async_from_config_file(
config_file,
hass,
verbose=args.verbose,
skip_pip=args.skip_pip, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days, safe_mode=args.safe_mode,
log_file=args.log_file,
log_no_color=args.log_no_color,
) )
if hass is None:
return 1
if args.open_ui and hass.config.api is not None: if args.open_ui and hass.config.api is not None:
import webbrowser import webbrowser
@ -358,7 +328,7 @@ def main() -> int:
return scripts.run(args.script) return scripts.run(args.script)
config_dir = os.path.join(os.getcwd(), args.config) config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
ensure_config_path(config_dir) ensure_config_path(config_dir)
# Daemon functions # Daemon functions

View File

@ -1,6 +1,5 @@
"""Provide methods to bootstrap a Home Assistant instance.""" """Provide methods to bootstrap a Home Assistant instance."""
import asyncio import asyncio
from collections import OrderedDict
import logging import logging
import logging.handlers import logging.handlers
import os import os
@ -11,6 +10,7 @@ from typing import Any, Dict, Optional, Set
import voluptuous as vol import voluptuous as vol
from homeassistant import config as conf_util, config_entries, core, loader from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.components import http
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_CLOSE,
REQUIRED_NEXT_PYTHON_DATE, REQUIRED_NEXT_PYTHON_DATE,
@ -42,25 +42,20 @@ STAGE_1_INTEGRATIONS = {
} }
async def async_from_config_dict( async def async_setup_hass(
config: Dict[str, Any], *,
hass: core.HomeAssistant, config_dir: str,
config_dir: Optional[str] = None, verbose: bool,
enable_log: bool = True, log_rotate_days: int,
verbose: bool = False, log_file: str,
skip_pip: bool = False, log_no_color: bool,
log_rotate_days: Any = None, skip_pip: bool,
log_file: Any = None, safe_mode: bool,
log_no_color: bool = False,
) -> Optional[core.HomeAssistant]: ) -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary. """Set up Home Assistant."""
hass = core.HomeAssistant()
hass.config.config_dir = config_dir
Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()
if enable_log:
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color) async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
hass.config.skip_pip = skip_pip hass.config.skip_pip = skip_pip
@ -69,6 +64,54 @@ async def async_from_config_dict(
"Skipping pip installation of required modules. This may cause issues" "Skipping pip installation of required modules. This may cause issues"
) )
if not await conf_util.async_ensure_config_exists(hass):
_LOGGER.error("Error getting configuration path")
return None
_LOGGER.info("Config directory: %s", config_dir)
config_dict = None
if not safe_mode:
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
try:
config_dict = await conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
_LOGGER.error(
"Failed to parse configuration.yaml: %s. Falling back to safe mode",
err,
)
else:
if not is_virtual_env():
await async_mount_local_lib_path(config_dir)
await async_from_config_dict(config_dict, hass)
finally:
clear_secret_cache()
if safe_mode or config_dict is None:
_LOGGER.info("Starting in safe mode")
http_conf = (await http.async_get_last_config(hass)) or {}
await async_from_config_dict(
{"safe_mode": {}, "http": http_conf}, hass,
)
return hass
async def async_from_config_dict(
config: Dict[str, Any], hass: core.HomeAssistant
) -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.
Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()
core_config = config.get(core.DOMAIN, {}) core_config = config.get(core.DOMAIN, {})
try: try:
@ -83,14 +126,6 @@ async def async_from_config_dict(
) )
return None return None
# Make a copy because we are mutating it.
config = OrderedDict(config)
# Merge packages
await conf_util.merge_packages_config(
hass, config, core_config.get(conf_util.CONF_PACKAGES, {})
)
hass.config_entries = config_entries.ConfigEntries(hass, config) hass.config_entries = config_entries.ConfigEntries(hass, config)
await hass.config_entries.async_initialize() await hass.config_entries.async_initialize()
@ -116,46 +151,6 @@ async def async_from_config_dict(
return hass return hass
async def async_from_config_file(
config_path: str,
hass: core.HomeAssistant,
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False,
) -> Optional[core.HomeAssistant]:
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter.
This method is a coroutine.
"""
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
if not is_virtual_env():
await async_mount_local_lib_path(config_dir)
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
try:
config_dict = await hass.async_add_executor_job(
conf_util.load_yaml_config_file, config_path
)
except HomeAssistantError as err:
_LOGGER.error("Error loading %s: %s", config_path, err)
return None
finally:
clear_secret_cache()
return await async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip
)
@core.callback @core.callback
def async_enable_logging( def async_enable_logging(
hass: core.HomeAssistant, hass: core.HomeAssistant,
@ -269,6 +264,7 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
domains = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN) domains = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN)
# Add config entry domains # Add config entry domains
if "safe_mode" not in config:
domains.update(hass.config_entries.async_domains()) domains.update(hass.config_entries.async_domains())
# Make sure the Hass.io component is loaded # Make sure the Hass.io component is loaded

View File

@ -21,11 +21,6 @@ _LOGGER = logging.getLogger(__name__)
ICON = "mdi:security" ICON = "mdi:security"
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode alarm control panel device.""" """Set up Abode alarm control panel device."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -13,11 +13,6 @@ from .const import DOMAIN, SIGNAL_TRIGGER_QUICK_ACTION
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode binary sensor devices.""" """Set up Abode binary sensor devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -18,11 +18,6 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode camera devices.""" """Set up Abode camera devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -11,11 +11,6 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode cover devices.""" """Set up Abode cover devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -24,11 +24,6 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode light devices.""" """Set up Abode light devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -11,11 +11,6 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode lock devices.""" """Set up Abode lock devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -3,7 +3,7 @@
"name": "Abode", "name": "Abode",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/abode", "documentation": "https://www.home-assistant.io/integrations/abode",
"requirements": ["abodepy==0.16.7"], "requirements": ["abodepy==0.17.0"],
"dependencies": [], "dependencies": [],
"codeowners": ["@shred86"] "codeowners": ["@shred86"]
} }

View File

@ -22,11 +22,6 @@ SENSOR_TYPES = {
} }
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode sensor devices.""" """Set up Abode sensor devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -12,11 +12,6 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode switch devices.""" """Set up Abode switch devices."""
data = hass.data[DOMAIN] data = hass.data[DOMAIN]

View File

@ -0,0 +1,10 @@
{
"config": {
"step": {
"hassio_confirm": {
"description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k AddGuard pomoc\u00ed hass.io {addon}?",
"title": "AdGuard prost\u0159ednictv\u00edm dopl\u0148ku Hass.io"
}
}
}
}

View File

@ -1,8 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"adguard_home_addon_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}. Bitte aktualisieren Sie Ihr Hass.io AdGuard Home Add-on.", "adguard_home_addon_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, du hast {current_version}. Bitte aktualisiere dein Hass.io AdGuard Home Add-on.",
"adguard_home_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}.", "adguard_home_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, du hast {current_version}.",
"existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.", "existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.",
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig." "single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig."
}, },

View File

@ -142,11 +142,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool
class AdGuardHomeEntity(Entity): class AdGuardHomeEntity(Entity):
"""Defines a base AdGuard Home entity.""" """Defines a base AdGuard Home entity."""
def __init__(self, adguard, name: str, icon: str) -> None: def __init__(
self, adguard, name: str, icon: str, enabled_default: bool = True
) -> None:
"""Initialize the AdGuard Home entity.""" """Initialize the AdGuard Home entity."""
self._name = name
self._icon = icon
self._available = True self._available = True
self._enabled_default = enabled_default
self._icon = icon
self._name = name
self.adguard = adguard self.adguard = adguard
@property @property
@ -159,6 +162,11 @@ class AdGuardHomeEntity(Entity):
"""Return the mdi icon of the entity.""" """Return the mdi icon of the entity."""
return self._icon return self._icon
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
return self._enabled_default
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
@ -166,6 +174,9 @@ class AdGuardHomeEntity(Entity):
async def async_update(self) -> None: async def async_update(self) -> None:
"""Update AdGuard Home entity.""" """Update AdGuard Home entity."""
if not self.enabled:
return
try: try:
await self._adguard_update() await self._adguard_update()
self._available = True self._available = True

View File

@ -51,14 +51,20 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity):
"""Defines a AdGuard Home sensor.""" """Defines a AdGuard Home sensor."""
def __init__( def __init__(
self, adguard, name: str, icon: str, measurement: str, unit_of_measurement: str self,
adguard,
name: str,
icon: str,
measurement: str,
unit_of_measurement: str,
enabled_default: bool = True,
) -> None: ) -> None:
"""Initialize AdGuard Home sensor.""" """Initialize AdGuard Home sensor."""
self._state = None self._state = None
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self.measurement = measurement self.measurement = measurement
super().__init__(adguard, name, icon) super().__init__(adguard, name, icon, enabled_default)
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
@ -109,6 +115,7 @@ class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor):
"mdi:magnify-close", "mdi:magnify-close",
"blocked_filtering", "blocked_filtering",
"queries", "queries",
enabled_default=False,
) )
async def _adguard_update(self) -> None: async def _adguard_update(self) -> None:
@ -214,7 +221,12 @@ class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
def __init__(self, adguard): def __init__(self, adguard):
"""Initialize AdGuard Home sensor.""" """Initialize AdGuard Home sensor."""
super().__init__( super().__init__(
adguard, "AdGuard Rules Count", "mdi:counter", "rules_count", "rules" adguard,
"AdGuard Rules Count",
"mdi:counter",
"rules_count",
"rules",
enabled_default=False,
) )
async def _adguard_update(self) -> None: async def _adguard_update(self) -> None:

View File

@ -10,9 +10,9 @@ from homeassistant.components.adguard.const import (
DATA_ADGUARD_VERION, DATA_ADGUARD_VERION,
DOMAIN, DOMAIN,
) )
from homeassistant.components.switch import SwitchDevice
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,14 +45,16 @@ async def async_setup_entry(
async_add_entities(switches, True) async_add_entities(switches, True)
class AdGuardHomeSwitch(ToggleEntity, AdGuardHomeDeviceEntity): class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchDevice):
"""Defines a AdGuard Home switch.""" """Defines a AdGuard Home switch."""
def __init__(self, adguard, name: str, icon: str, key: str): def __init__(
self, adguard, name: str, icon: str, key: str, enabled_default: bool = True
):
"""Initialize AdGuard Home switch.""" """Initialize AdGuard Home switch."""
self._state = False self._state = False
self._key = key self._key = key
super().__init__(adguard, name, icon) super().__init__(adguard, name, icon, enabled_default)
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
@ -204,7 +206,13 @@ class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch):
def __init__(self, adguard) -> None: def __init__(self, adguard) -> None:
"""Initialize AdGuard Home switch.""" """Initialize AdGuard Home switch."""
super().__init__(adguard, "AdGuard Query Log", "mdi:shield-check", "querylog") super().__init__(
adguard,
"AdGuard Query Log",
"mdi:shield-check",
"querylog",
enabled_default=False,
)
async def _adguard_turn_off(self) -> None: async def _adguard_turn_off(self) -> None:
"""Turn off the switch.""" """Turn off the switch."""

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Airly-integration for disse koordinater er allerede konfigureret."
},
"error": { "error": {
"auth": "API-n\u00f8glen er ikke korrekt.", "auth": "API-n\u00f8glen er ikke korrekt.",
"name_exists": "Navnet findes allerede.", "name_exists": "Navnet findes allerede.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Die Airly-Integration ist f\u00fcr diese Koordinaten bereits konfiguriert."
},
"error": { "error": {
"auth": "Der API-Schl\u00fcssel ist nicht korrekt.", "auth": "Der API-Schl\u00fcssel ist nicht korrekt.",
"name_exists": "Name existiert bereits", "name_exists": "Name existiert bereits",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Airly integration for these coordinates is already configured."
},
"error": { "error": {
"auth": "API key is not correct.", "auth": "API key is not correct.",
"name_exists": "Name already exists.", "name_exists": "Name already exists.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "La integraci\u00f3n a\u00e9rea para estas coordenadas ya est\u00e1 configurada."
},
"error": { "error": {
"auth": "La clave de la API no es correcta.", "auth": "La clave de la API no es correcta.",
"name_exists": "El nombre ya existe.", "name_exists": "El nombre ya existe.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "L'int\u00e9gration des coordonn\u00e9es d'Airly est d\u00e9j\u00e0 configur\u00e9."
},
"error": { "error": {
"auth": "La cl\u00e9 API n'est pas correcte.", "auth": "La cl\u00e9 API n'est pas correcte.",
"name_exists": "Le nom existe d\u00e9j\u00e0.", "name_exists": "Le nom existe d\u00e9j\u00e0.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "L'integrazione Airly per queste coordinate \u00e8 gi\u00e0 configurata."
},
"error": { "error": {
"auth": "La chiave API non \u00e8 corretta.", "auth": "La chiave API non \u00e8 corretta.",
"name_exists": "Il nome \u00e8 gi\u00e0 esistente", "name_exists": "Il nome \u00e8 gi\u00e0 esistente",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\uc774 \uc88c\ud45c\uc5d0 \ub300\ud55c Airly \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4."
},
"error": { "error": {
"auth": "API \ud0a4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", "auth": "API \ud0a4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.",
"name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4.", "name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Airly Integratioun fir d\u00ebs Koordinaten ass scho konfigur\u00e9iert."
},
"error": { "error": {
"auth": "Api Schl\u00ebssel ass net korrekt.", "auth": "Api Schl\u00ebssel ass net korrekt.",
"name_exists": "Numm g\u00ebtt et schonn", "name_exists": "Numm g\u00ebtt et schonn",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Airly integrering for disse koordinatene er allerede konfigurert."
},
"error": { "error": {
"auth": "API-n\u00f8kkelen er ikke korrekt.", "auth": "API-n\u00f8kkelen er ikke korrekt.",
"name_exists": "Navnet finnes allerede.", "name_exists": "Navnet finnes allerede.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "Integracja Airly dla tych wsp\u00f3\u0142rz\u0119dnych jest ju\u017c skonfigurowana."
},
"error": { "error": {
"auth": "Klucz API jest nieprawid\u0142owy.", "auth": "Klucz API jest nieprawid\u0142owy.",
"name_exists": "Nazwa ju\u017c istnieje.", "name_exists": "Nazwa ju\u017c istnieje.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f Airly \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u0436\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c\u0438 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430."
},
"error": { "error": {
"auth": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", "auth": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.",
"name_exists": "\u042d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", "name_exists": "\u042d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.",

View File

@ -1,5 +1,8 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u6b64 Airly \u6574\u5408\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002"
},
"error": { "error": {
"auth": "API \u5bc6\u9470\u4e0d\u6b63\u78ba\u3002", "auth": "API \u5bc6\u9470\u4e0d\u6b63\u78ba\u3002",
"name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728", "name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728",

View File

@ -41,6 +41,12 @@ async def async_setup_entry(hass, config_entry):
latitude = config_entry.data[CONF_LATITUDE] latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE] longitude = config_entry.data[CONF_LONGITUDE]
# For backwards compat, set unique ID
if config_entry.unique_id is None:
hass.config_entries.async_update_entry(
config_entry, unique_id=f"{latitude}-{longitude}"
)
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
airly = AirlyData(websession, api_key, latitude, longitude) airly = AirlyData(websession, api_key, latitude, longitude)

View File

@ -5,7 +5,7 @@ from homeassistant.components.air_quality import (
ATTR_PM_10, ATTR_PM_10,
AirQualityEntity, AirQualityEntity,
) )
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.const import CONF_NAME
from .const import ( from .const import (
ATTR_API_ADVICE, ATTR_API_ADVICE,
@ -35,13 +35,10 @@ LABEL_PM_10_PERCENT = f"{ATTR_PM_10}_percent_of_limit"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Airly air_quality entity based on a config entry.""" """Set up Airly air_quality entity based on a config entry."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
unique_id = f"{latitude}-{longitude}"
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
async_add_entities([AirlyAirQuality(data, name, unique_id)], True) async_add_entities([AirlyAirQuality(data, name, config_entry.unique_id)], True)
def round_state(func): def round_state(func):

View File

@ -6,19 +6,14 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS from .const import ( # pylint:disable=unused-import
DEFAULT_NAME,
DOMAIN,
@callback NO_AIRLY_SENSORS,
def configured_instances(hass): )
"""Return a set of configured Airly instances."""
return set(
entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN)
)
class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@ -38,8 +33,10 @@ class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
if user_input is not None: if user_input is not None:
if user_input[CONF_NAME] in configured_instances(self.hass): await self.async_set_unique_id(
self._errors[CONF_NAME] = "name_exists" f"{user_input[CONF_LATITUDE]}-{user_input[CONF_LONGITUDE]}"
)
self._abort_if_unique_id_configured()
api_key_valid = await self._test_api_key(websession, user_input["api_key"]) api_key_valid = await self._test_api_key(websession, user_input["api_key"])
if not api_key_valid: if not api_key_valid:
self._errors["base"] = "auth" self._errors["base"] = "auth"

View File

@ -2,8 +2,6 @@
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME, CONF_NAME,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
@ -62,14 +60,12 @@ SENSOR_TYPES = {
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Airly sensor entities based on a config entry.""" """Set up Airly sensor entities based on a config entry."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
sensors = [] sensors = []
for sensor in SENSOR_TYPES: for sensor in SENSOR_TYPES:
unique_id = f"{latitude}-{longitude}-{sensor.lower()}" unique_id = f"{config_entry.unique_id}-{sensor.lower()}"
sensors.append(AirlySensor(data, name, sensor, unique_id)) sensors.append(AirlySensor(data, name, sensor, unique_id))
async_add_entities(sensors, True) async_add_entities(sensors, True)

View File

@ -14,9 +14,11 @@
} }
}, },
"error": { "error": {
"name_exists": "Name already exists.",
"wrong_location": "No Airly measuring stations in this area.", "wrong_location": "No Airly measuring stations in this area.",
"auth": "API key is not correct." "auth": "API key is not correct."
},
"abort": {
"already_configured": "Airly integration for these coordinates is already configured."
} }
} }
} }

View File

@ -1,5 +1,12 @@
{ {
"device_automation": { "device_automation": {
"action_type": {
"arm_away": "Aktiviere {entity_name} Unterwegs",
"arm_home": "Aktiviere {entity_name} Zuhause",
"arm_night": "Aktiviere {entity_name} Nacht-Modus",
"disarm": "Deaktivere {entity_name}",
"trigger": "Ausl\u00f6ser {entity_name}"
},
"trigger_type": { "trigger_type": {
"armed_away": "{entity_name} Unterwegs", "armed_away": "{entity_name} Unterwegs",
"armed_home": "{entity_name} Zuhause", "armed_home": "{entity_name} Zuhause",

View File

@ -121,67 +121,49 @@ class AlarmControlPanel(Entity):
"""Send disarm command.""" """Send disarm command."""
raise NotImplementedError() raise NotImplementedError()
def async_alarm_disarm(self, code=None): async def async_alarm_disarm(self, code=None):
"""Send disarm command. """Send disarm command."""
await self.hass.async_add_executor_job(self.alarm_disarm, code)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_executor_job(self.alarm_disarm, code)
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
"""Send arm home command.""" """Send arm home command."""
raise NotImplementedError() raise NotImplementedError()
def async_alarm_arm_home(self, code=None): async def async_alarm_arm_home(self, code=None):
"""Send arm home command. """Send arm home command."""
await self.hass.async_add_executor_job(self.alarm_arm_home, code)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_executor_job(self.alarm_arm_home, code)
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
"""Send arm away command.""" """Send arm away command."""
raise NotImplementedError() raise NotImplementedError()
def async_alarm_arm_away(self, code=None): async def async_alarm_arm_away(self, code=None):
"""Send arm away command. """Send arm away command."""
await self.hass.async_add_executor_job(self.alarm_arm_away, code)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_executor_job(self.alarm_arm_away, code)
def alarm_arm_night(self, code=None): def alarm_arm_night(self, code=None):
"""Send arm night command.""" """Send arm night command."""
raise NotImplementedError() raise NotImplementedError()
def async_alarm_arm_night(self, code=None): async def async_alarm_arm_night(self, code=None):
"""Send arm night command. """Send arm night command."""
await self.hass.async_add_executor_job(self.alarm_arm_night, code)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_executor_job(self.alarm_arm_night, code)
def alarm_trigger(self, code=None): def alarm_trigger(self, code=None):
"""Send alarm trigger command.""" """Send alarm trigger command."""
raise NotImplementedError() raise NotImplementedError()
def async_alarm_trigger(self, code=None): async def async_alarm_trigger(self, code=None):
"""Send alarm trigger command. """Send alarm trigger command."""
await self.hass.async_add_executor_job(self.alarm_trigger, code)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_executor_job(self.alarm_trigger, code)
def alarm_arm_custom_bypass(self, code=None): def alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command.""" """Send arm custom bypass command."""
raise NotImplementedError() raise NotImplementedError()
def async_alarm_arm_custom_bypass(self, code=None): async def async_alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command. """Send arm custom bypass command."""
await self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code)
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code)
@property @property
@abstractmethod @abstractmethod

View File

@ -118,11 +118,12 @@ def setup(hass, config):
conf = config.get(DOMAIN) conf = config.get(DOMAIN)
restart = False restart = False
device = conf.get(CONF_DEVICE) device = conf[CONF_DEVICE]
display = conf.get(CONF_PANEL_DISPLAY) display = conf[CONF_PANEL_DISPLAY]
auto_bypass = conf[CONF_AUTO_BYPASS]
zones = conf.get(CONF_ZONES) zones = conf.get(CONF_ZONES)
device_type = device.get(CONF_DEVICE_TYPE) device_type = device[CONF_DEVICE_TYPE]
host = DEFAULT_DEVICE_HOST host = DEFAULT_DEVICE_HOST
port = DEFAULT_DEVICE_PORT port = DEFAULT_DEVICE_PORT
path = DEFAULT_DEVICE_PATH path = DEFAULT_DEVICE_PATH
@ -204,7 +205,9 @@ def setup(hass, config):
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
load_platform(hass, "alarm_control_panel", DOMAIN, conf, config) load_platform(
hass, "alarm_control_panel", DOMAIN, {CONF_AUTO_BYPASS: auto_bypass}, config
)
if zones: if zones:
load_platform(hass, "binary_sensor", DOMAIN, {CONF_ZONES: zones}, config) load_platform(hass, "binary_sensor", DOMAIN, {CONF_ZONES: zones}, config)

View File

@ -21,7 +21,7 @@ from homeassistant.const import (
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DATA_AD, DOMAIN, SIGNAL_PANEL_MESSAGE from . import CONF_AUTO_BYPASS, DATA_AD, DOMAIN, SIGNAL_PANEL_MESSAGE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -35,13 +35,17 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({vol.Required(ATTR_KEYPRESS): cv.string})
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up for AlarmDecoder alarm panels.""" """Set up for AlarmDecoder alarm panels."""
device = AlarmDecoderAlarmPanel(discovery_info["autobypass"]) if discovery_info is None:
add_entities([device]) return
auto_bypass = discovery_info[CONF_AUTO_BYPASS]
entity = AlarmDecoderAlarmPanel(auto_bypass)
add_entities([entity])
def alarm_toggle_chime_handler(service): def alarm_toggle_chime_handler(service):
"""Register toggle chime handler.""" """Register toggle chime handler."""
code = service.data.get(ATTR_CODE) code = service.data.get(ATTR_CODE)
device.alarm_toggle_chime(code) entity.alarm_toggle_chime(code)
hass.services.register( hass.services.register(
DOMAIN, DOMAIN,
@ -53,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
def alarm_keypress_handler(service): def alarm_keypress_handler(service):
"""Register keypress handler.""" """Register keypress handler."""
keypress = service.data[ATTR_KEYPRESS] keypress = service.data[ATTR_KEYPRESS]
device.alarm_keypress(keypress) entity.alarm_keypress(keypress)
hass.services.register( hass.services.register(
DOMAIN, DOMAIN,

View File

@ -2,7 +2,9 @@
"domain": "alarmdecoder", "domain": "alarmdecoder",
"name": "AlarmDecoder", "name": "AlarmDecoder",
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
"requirements": ["alarmdecoder==1.13.9"], "requirements": [
"alarmdecoder==1.13.2"
],
"dependencies": [], "dependencies": [],
"codeowners": [] "codeowners": []
} }

View File

@ -115,7 +115,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
await self._alarm.async_alarm_disarm() await self._alarm.async_alarm_disarm()
async def async_alarm_arm_home(self, code=None): async def async_alarm_arm_home(self, code=None):
"""Send arm hom command.""" """Send arm home command."""
if self._validate_code(code): if self._validate_code(code):
await self._alarm.async_alarm_arm_home() await self._alarm.async_alarm_arm_home()

View File

@ -1,8 +1,20 @@
"""Alexa capabilities.""" """Alexa capabilities."""
import logging import logging
from homeassistant.components import cover, fan, image_processing, input_number, light from homeassistant.components import (
cover,
fan,
image_processing,
input_number,
light,
vacuum,
)
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
)
import homeassistant.components.climate.const as climate import homeassistant.components.climate.const as climate
import homeassistant.components.media_player.const as media_player import homeassistant.components.media_player.const as media_player
from homeassistant.const import ( from homeassistant.const import (
@ -31,7 +43,6 @@ from .const import (
API_THERMOSTAT_PRESETS, API_THERMOSTAT_PRESETS,
DATE_FORMAT, DATE_FORMAT,
PERCENTAGE_FAN_MAP, PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP,
Inputs, Inputs,
) )
from .errors import UnsupportedProperty from .errors import UnsupportedProperty
@ -503,6 +514,10 @@ class AlexaColorController(AlexaCapability):
"""Return what properties this entity supports.""" """Return what properties this entity supports."""
return [{"name": "color"}] return [{"name": "color"}]
def properties_proactively_reported(self):
"""Return True if properties asynchronously reported."""
return True
def properties_retrievable(self): def properties_retrievable(self):
"""Return True if properties can be retrieved.""" """Return True if properties can be retrieved."""
return True return True
@ -548,6 +563,10 @@ class AlexaColorTemperatureController(AlexaCapability):
"""Return what properties this entity supports.""" """Return what properties this entity supports."""
return [{"name": "colorTemperatureInKelvin"}] return [{"name": "colorTemperatureInKelvin"}]
def properties_proactively_reported(self):
"""Return True if properties asynchronously reported."""
return True
def properties_retrievable(self): def properties_retrievable(self):
"""Return True if properties can be retrieved.""" """Return True if properties can be retrieved."""
return True return True
@ -590,6 +609,10 @@ class AlexaPercentageController(AlexaCapability):
"""Return what properties this entity supports.""" """Return what properties this entity supports."""
return [{"name": "percentage"}] return [{"name": "percentage"}]
def properties_proactively_reported(self):
"""Return True if properties asynchronously reported."""
return True
def properties_retrievable(self): def properties_retrievable(self):
"""Return True if properties can be retrieved.""" """Return True if properties can be retrieved."""
return True return True
@ -1064,10 +1087,23 @@ class AlexaSecurityPanelController(AlexaCapability):
def configuration(self): def configuration(self):
"""Return configuration object with supported authorization types.""" """Return configuration object with supported authorization types."""
code_format = self.entity.attributes.get(ATTR_CODE_FORMAT) code_format = self.entity.attributes.get(ATTR_CODE_FORMAT)
supported = self.entity.attributes[ATTR_SUPPORTED_FEATURES]
configuration = {}
supported_arm_states = [{"value": "DISARMED"}]
if supported & SUPPORT_ALARM_ARM_AWAY:
supported_arm_states.append({"value": "ARMED_AWAY"})
if supported & SUPPORT_ALARM_ARM_HOME:
supported_arm_states.append({"value": "ARMED_STAY"})
if supported & SUPPORT_ALARM_ARM_NIGHT:
supported_arm_states.append({"value": "ARMED_NIGHT"})
configuration["supportedArmStates"] = supported_arm_states
if code_format == FORMAT_NUMBER: if code_format == FORMAT_NUMBER:
return {"supportedAuthorizationTypes": [{"type": "FOUR_DIGIT_PIN"}]} configuration["supportedAuthorizationTypes"] = [{"type": "FOUR_DIGIT_PIN"}]
return None
return configuration
class AlexaModeController(AlexaCapability): class AlexaModeController(AlexaCapability):
@ -1185,7 +1221,10 @@ class AlexaModeController(AlexaCapability):
f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}", f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}",
[AlexaGlobalCatalog.VALUE_CLOSE], [AlexaGlobalCatalog.VALUE_CLOSE],
) )
self._resource.add_mode(f"{cover.ATTR_POSITION}.custom", ["Custom"]) self._resource.add_mode(
f"{cover.ATTR_POSITION}.custom",
["Custom", AlexaGlobalCatalog.SETTING_PRESET],
)
return self._resource.serialize_capability_resources() return self._resource.serialize_capability_resources()
return None return None
@ -1287,10 +1326,20 @@ class AlexaRangeController(AlexaCapability):
if name != "rangeValue": if name != "rangeValue":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
# Return None for unavailable and unknown states.
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
if self.entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return None
# Fan Speed # Fan Speed
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
speed_list = self.entity.attributes.get(fan.ATTR_SPEED_LIST)
speed = self.entity.attributes.get(fan.ATTR_SPEED) speed = self.entity.attributes.get(fan.ATTR_SPEED)
return RANGE_FAN_MAP.get(speed, 0) if speed_list is not None and speed is not None:
speed_index = next(
(i for i, v in enumerate(speed_list) if v == speed), None
)
return speed_index
# Cover Position # Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
@ -1304,6 +1353,16 @@ class AlexaRangeController(AlexaCapability):
if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
return float(self.entity.state) return float(self.entity.state)
# Vacuum Fan Speed
if self.instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
speed_list = self.entity.attributes.get(vacuum.ATTR_FAN_SPEED_LIST)
speed = self.entity.attributes.get(vacuum.ATTR_FAN_SPEED)
if speed_list is not None and speed is not None:
speed_index = next(
(i for i, v in enumerate(speed_list) if v == speed), None
)
return speed_index
return None return None
def configuration(self): def configuration(self):
@ -1318,24 +1377,26 @@ class AlexaRangeController(AlexaCapability):
# Fan Speed Resources # Fan Speed Resources
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
speed_list = self.entity.attributes[fan.ATTR_SPEED_LIST]
max_value = len(speed_list) - 1
self._resource = AlexaPresetResource( self._resource = AlexaPresetResource(
labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED], labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED],
min_value=1, min_value=0,
max_value=3, max_value=max_value,
precision=1, precision=1,
) )
self._resource.add_preset( for index, speed in enumerate(speed_list):
value=1, labels = []
labels=[AlexaGlobalCatalog.VALUE_LOW, AlexaGlobalCatalog.VALUE_MINIMUM], if isinstance(speed, str):
) labels.append(speed.replace("_", " "))
self._resource.add_preset(value=2, labels=[AlexaGlobalCatalog.VALUE_MEDIUM]) if index == 1:
self._resource.add_preset( labels.append(AlexaGlobalCatalog.VALUE_MINIMUM)
value=3, if index == max_value:
labels=[ labels.append(AlexaGlobalCatalog.VALUE_MAXIMUM)
AlexaGlobalCatalog.VALUE_HIGH,
AlexaGlobalCatalog.VALUE_MAXIMUM, if len(labels) > 0:
], self._resource.add_preset(value=index, labels=labels)
)
return self._resource.serialize_capability_resources() return self._resource.serialize_capability_resources()
# Cover Position Resources # Cover Position Resources
@ -1368,7 +1429,7 @@ class AlexaRangeController(AlexaCapability):
unit = self.entity.attributes.get(input_number.ATTR_UNIT_OF_MEASUREMENT) unit = self.entity.attributes.get(input_number.ATTR_UNIT_OF_MEASUREMENT)
self._resource = AlexaPresetResource( self._resource = AlexaPresetResource(
["Value"], ["Value", AlexaGlobalCatalog.SETTING_PRESET],
min_value=min_value, min_value=min_value,
max_value=max_value, max_value=max_value,
precision=precision, precision=precision,
@ -1382,6 +1443,26 @@ class AlexaRangeController(AlexaCapability):
) )
return self._resource.serialize_capability_resources() return self._resource.serialize_capability_resources()
# Vacuum Fan Speed Resources
if self.instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
speed_list = self.entity.attributes[vacuum.ATTR_FAN_SPEED_LIST]
max_value = len(speed_list) - 1
self._resource = AlexaPresetResource(
labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED],
min_value=0,
max_value=max_value,
precision=1,
)
for index, speed in enumerate(speed_list):
labels = [speed.replace("_", " ")]
if index == 1:
labels.append(AlexaGlobalCatalog.VALUE_MINIMUM)
if index == max_value:
labels.append(AlexaGlobalCatalog.VALUE_MAXIMUM)
self._resource.add_preset(value=index, labels=labels)
return self._resource.serialize_capability_resources()
return None return None
def semantics(self): def semantics(self):
@ -1701,3 +1782,29 @@ class AlexaEqualizerController(AlexaCapability):
configurations = {"modes": {"supported": supported_sound_modes}} configurations = {"modes": {"supported": supported_sound_modes}}
return configurations return configurations
class AlexaTimeHoldController(AlexaCapability):
"""Implements Alexa.TimeHoldController.
https://developer.amazon.com/docs/device-apis/alexa-timeholdcontroller.html
"""
supported_locales = {"en-US"}
def __init__(self, entity, allow_remote_resume=False):
"""Initialize the entity."""
super().__init__(entity)
self._allow_remote_resume = allow_remote_resume
def name(self):
"""Return the Alexa API name of this interface."""
return "Alexa.TimeHoldController"
def configuration(self):
"""Return configuration object.
Set allowRemoteResume to True if Alexa can restart the operation on the device.
When false, Alexa does not send the Resume directive.
"""
return {"allowRemoteResume": self._allow_remote_resume}

View File

@ -84,20 +84,6 @@ PERCENTAGE_FAN_MAP = {
fan.SPEED_HIGH: 100, fan.SPEED_HIGH: 100,
} }
RANGE_FAN_MAP = {
fan.SPEED_OFF: 0,
fan.SPEED_LOW: 1,
fan.SPEED_MEDIUM: 2,
fan.SPEED_HIGH: 3,
}
SPEED_FAN_MAP = {
0: fan.SPEED_OFF,
1: fan.SPEED_LOW,
2: fan.SPEED_MEDIUM,
3: fan.SPEED_HIGH,
}
class Cause: class Cause:
"""Possible causes for property changes. """Possible causes for property changes.

View File

@ -19,6 +19,8 @@ from homeassistant.components import (
script, script,
sensor, sensor,
switch, switch,
timer,
vacuum,
) )
from homeassistant.components.climate import const as climate from homeassistant.components.climate import const as climate
from homeassistant.const import ( from homeassistant.const import (
@ -61,6 +63,7 @@ from .capabilities import (
AlexaStepSpeaker, AlexaStepSpeaker,
AlexaTemperatureSensor, AlexaTemperatureSensor,
AlexaThermostatController, AlexaThermostatController,
AlexaTimeHoldController,
AlexaToggleController, AlexaToggleController,
) )
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
@ -255,6 +258,9 @@ class AlexaEntity:
def serialize_properties(self): def serialize_properties(self):
"""Yield each supported property in API format.""" """Yield each supported property in API format."""
for interface in self.interfaces(): for interface in self.interfaces():
if not interface.properties_proactively_reported():
continue
for prop in interface.serialize_properties(): for prop in interface.serialize_properties():
yield prop yield prop
@ -394,6 +400,7 @@ class CoverCapabilities(AlexaEntity):
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION: if supported & cover.SUPPORT_SET_POSITION:
yield AlexaRangeController( yield AlexaRangeController(
@ -703,3 +710,48 @@ class InputNumberCapabilities(AlexaEntity):
) )
yield AlexaEndpointHealth(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass) yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(timer.DOMAIN)
class TimerCapabilities(AlexaEntity):
"""Class to represent Timer capabilities."""
def default_display_categories(self):
"""Return the display categories for this entity."""
return [DisplayCategory.OTHER]
def interfaces(self):
"""Yield the supported interfaces."""
yield AlexaTimeHoldController(self.entity, allow_remote_resume=True)
yield Alexa(self.entity)
@ENTITY_ADAPTERS.register(vacuum.DOMAIN)
class VacuumCapabilities(AlexaEntity):
"""Class to represent vacuum capabilities."""
def default_display_categories(self):
"""Return the display categories for this entity."""
return [DisplayCategory.OTHER]
def interfaces(self):
"""Yield the supported interfaces."""
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (supported & vacuum.SUPPORT_TURN_ON) and (
supported & vacuum.SUPPORT_TURN_OFF
):
yield AlexaPowerController(self.entity)
if supported & vacuum.SUPPORT_FAN_SPEED:
yield AlexaRangeController(
self.entity, instance=f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}"
)
if supported & vacuum.SUPPORT_PAUSE:
support_resume = bool(supported & vacuum.SUPPORT_START)
yield AlexaTimeHoldController(
self.entity, allow_remote_resume=support_resume
)
yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)

View File

@ -10,6 +10,8 @@ from homeassistant.components import (
input_number, input_number,
light, light,
media_player, media_player,
timer,
vacuum,
) )
from homeassistant.components.climate import const as climate from homeassistant.components.climate import const as climate
from homeassistant.const import ( from homeassistant.const import (
@ -50,8 +52,6 @@ from .const import (
API_THERMOSTAT_MODES_CUSTOM, API_THERMOSTAT_MODES_CUSTOM,
API_THERMOSTAT_PRESETS, API_THERMOSTAT_PRESETS,
PERCENTAGE_FAN_MAP, PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP,
SPEED_FAN_MAP,
Cause, Cause,
Inputs, Inputs,
) )
@ -119,7 +119,9 @@ async def async_api_turn_on(hass, config, directive, context):
domain = ha.DOMAIN domain = ha.DOMAIN
service = SERVICE_TURN_ON service = SERVICE_TURN_ON
if domain == media_player.DOMAIN: if domain == cover.DOMAIN:
service = cover.SERVICE_OPEN_COVER
elif domain == media_player.DOMAIN:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
if not supported & power_features: if not supported & power_features:
@ -145,7 +147,9 @@ async def async_api_turn_off(hass, config, directive, context):
domain = ha.DOMAIN domain = ha.DOMAIN
service = SERVICE_TURN_OFF service = SERVICE_TURN_OFF
if domain == media_player.DOMAIN: if entity.domain == cover.DOMAIN:
service = cover.SERVICE_CLOSE_COVER
elif domain == media_player.DOMAIN:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
if not supported & power_features: if not supported & power_features:
@ -908,8 +912,11 @@ async def async_api_arm(hass, config, directive, context):
entity.domain, service, data, blocking=False, context=context entity.domain, service, data, blocking=False, context=context
) )
# return 0 until alarm integration supports an exit delay
payload = {"exitDelayInSeconds": 0}
response = directive.response( response = directive.response(
name="Arm.Response", namespace="Alexa.SecurityPanelController" name="Arm.Response", namespace="Alexa.SecurityPanelController", payload=payload
) )
response.add_context_property( response.add_context_property(
@ -928,6 +935,12 @@ async def async_api_disarm(hass, config, directive, context):
"""Process a Security Panel Disarm request.""" """Process a Security Panel Disarm request."""
entity = directive.entity entity = directive.entity
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
response = directive.response()
# Per Alexa Documentation: If you receive a Disarm directive, and the system is already disarmed,
# respond with a success response, not an error response.
if entity.state == STATE_ALARM_DISARMED:
return response
payload = directive.payload payload = directive.payload
if "authorization" in payload: if "authorization" in payload:
@ -941,7 +954,6 @@ async def async_api_disarm(hass, config, directive, context):
msg = "Invalid Code" msg = "Invalid Code"
raise AlexaSecurityPanelUnauthorizedError(msg) raise AlexaSecurityPanelUnauthorizedError(msg)
response = directive.response()
response.add_context_property( response.add_context_property(
{ {
"name": "armState", "name": "armState",
@ -1095,8 +1107,10 @@ async def async_api_set_range(hass, config, directive, context):
# Fan Speed # Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_value = int(range_value)
service = fan.SERVICE_SET_SPEED service = fan.SERVICE_SET_SPEED
speed = SPEED_FAN_MAP.get(int(range_value)) speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
speed = next((v for i, v in enumerate(speed_list) if i == range_value), None)
if not speed: if not speed:
msg = "Entity does not support value" msg = "Entity does not support value"
@ -1127,7 +1141,7 @@ async def async_api_set_range(hass, config, directive, context):
service = cover.SERVICE_OPEN_COVER_TILT service = cover.SERVICE_OPEN_COVER_TILT
else: else:
service = cover.SERVICE_SET_COVER_TILT_POSITION service = cover.SERVICE_SET_COVER_TILT_POSITION
data[cover.ATTR_POSITION] = range_value data[cover.ATTR_TILT_POSITION] = range_value
# Input Number Value # Input Number Value
elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
@ -1137,6 +1151,20 @@ async def async_api_set_range(hass, config, directive, context):
max_value = float(entity.attributes[input_number.ATTR_MAX]) max_value = float(entity.attributes[input_number.ATTR_MAX])
data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value)) data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
# Vacuum Fan Speed
elif instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
service = vacuum.SERVICE_SET_FAN_SPEED
speed_list = entity.attributes[vacuum.ATTR_FAN_SPEED_LIST]
speed = next(
(v for i, v in enumerate(speed_list) if i == int(range_value)), None
)
if not speed:
msg = "Entity does not support value"
raise AlexaInvalidValueError(msg)
data[vacuum.ATTR_FAN_SPEED] = speed
else: else:
msg = "Entity does not support directive" msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg) raise AlexaInvalidDirectiveError(msg)
@ -1167,15 +1195,23 @@ async def async_api_adjust_range(hass, config, directive, context):
service = None service = None
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
range_delta = directive.payload["rangeValueDelta"] range_delta = directive.payload["rangeValueDelta"]
range_delta_default = bool(directive.payload["rangeValueDeltaDefault"])
response_value = 0 response_value = 0
# Fan Speed # Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_delta = int(range_delta) range_delta = int(range_delta)
service = fan.SERVICE_SET_SPEED service = fan.SERVICE_SET_SPEED
current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0) speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
speed = SPEED_FAN_MAP.get( current_speed = entity.attributes[fan.ATTR_SPEED]
min(3, max(0, range_delta + current_range)), fan.SPEED_OFF current_speed_index = next(
(i for i, v in enumerate(speed_list) if v == current_speed), 0
)
new_speed_index = min(
len(speed_list) - 1, max(0, current_speed_index + range_delta)
)
speed = next(
(v for i, v in enumerate(speed_list) if i == new_speed_index), None
) )
if speed == fan.SPEED_OFF: if speed == fan.SPEED_OFF:
@ -1185,21 +1221,37 @@ async def async_api_adjust_range(hass, config, directive, context):
# Cover Position # Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_delta = int(range_delta) range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
service = SERVICE_SET_COVER_POSITION service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION) current = entity.attributes.get(cover.ATTR_POSITION)
data[cover.ATTR_POSITION] = response_value = min( if not current:
100, max(0, range_delta + current) msg = "Unable to determine {} current position".format(entity.entity_id)
) raise AlexaInvalidValueError(msg)
position = response_value = min(100, max(0, range_delta + current))
if position == 100:
service = cover.SERVICE_OPEN_COVER
elif position == 0:
service = cover.SERVICE_CLOSE_COVER
else:
data[cover.ATTR_POSITION] = position
# Cover Tilt # Cover Tilt
elif instance == f"{cover.DOMAIN}.tilt": elif instance == f"{cover.DOMAIN}.tilt":
range_delta = int(range_delta) range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
service = SERVICE_SET_COVER_TILT_POSITION service = SERVICE_SET_COVER_TILT_POSITION
current = entity.attributes.get(cover.ATTR_TILT_POSITION) current = entity.attributes.get(cover.ATTR_TILT_POSITION)
data[cover.ATTR_TILT_POSITION] = response_value = min( if not current:
100, max(0, range_delta + current) msg = "Unable to determine {} current tilt position".format(
entity.entity_id
) )
raise AlexaInvalidValueError(msg)
tilt_position = response_value = min(100, max(0, range_delta + current))
if tilt_position == 100:
service = cover.SERVICE_OPEN_COVER_TILT
elif tilt_position == 0:
service = cover.SERVICE_CLOSE_COVER_TILT
else:
data[cover.ATTR_TILT_POSITION] = tilt_position
# Input Number Value # Input Number Value
elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}": elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
@ -1212,6 +1264,24 @@ async def async_api_adjust_range(hass, config, directive, context):
max_value, max(min_value, range_delta + current) max_value, max(min_value, range_delta + current)
) )
# Vacuum Fan Speed
elif instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
range_delta = int(range_delta)
service = vacuum.SERVICE_SET_FAN_SPEED
speed_list = entity.attributes[vacuum.ATTR_FAN_SPEED_LIST]
current_speed = entity.attributes[vacuum.ATTR_FAN_SPEED]
current_speed_index = next(
(i for i, v in enumerate(speed_list) if v == current_speed), 0
)
new_speed_index = min(
len(speed_list) - 1, max(0, current_speed_index + range_delta)
)
speed = next(
(v for i, v in enumerate(speed_list) if i == new_speed_index), None
)
data[vacuum.ATTR_FAN_SPEED] = response_value = speed
else: else:
msg = "Entity does not support directive" msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg) raise AlexaInvalidDirectiveError(msg)
@ -1396,3 +1466,49 @@ async def async_api_bands_directive(hass, config, directive, context):
# Currently bands directives are not supported. # Currently bands directives are not supported.
msg = "Entity does not support directive" msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg) raise AlexaInvalidDirectiveError(msg)
@HANDLERS.register(("Alexa.TimeHoldController", "Hold"))
async def async_api_hold(hass, config, directive, context):
"""Process a TimeHoldController Hold request."""
entity = directive.entity
data = {ATTR_ENTITY_ID: entity.entity_id}
if entity.domain == timer.DOMAIN:
service = timer.SERVICE_PAUSE
elif entity.domain == vacuum.DOMAIN:
service = vacuum.SERVICE_START_PAUSE
else:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
await hass.services.async_call(
entity.domain, service, data, blocking=False, context=context
)
return directive.response()
@HANDLERS.register(("Alexa.TimeHoldController", "Resume"))
async def async_api_resume(hass, config, directive, context):
"""Process a TimeHoldController Resume request."""
entity = directive.entity
data = {ATTR_ENTITY_ID: entity.entity_id}
if entity.domain == timer.DOMAIN:
service = timer.SERVICE_START
elif entity.domain == vacuum.DOMAIN:
service = vacuum.SERVICE_START_PAUSE
else:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
await hass.services.async_call(
entity.domain, service, data, blocking=False, context=context
)
return directive.response()

View File

@ -6,6 +6,9 @@
"missing_configuration": "Consulta la documentaci\u00f3 sobre com configurar Almond." "missing_configuration": "Consulta la documentaci\u00f3 sobre com configurar Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"title": "Almond (complement de Hass.io)"
},
"pick_implementation": { "pick_implementation": {
"title": "Selecci\u00f3 del m\u00e8tode d'autenticaci\u00f3" "title": "Selecci\u00f3 del m\u00e8tode d'autenticaci\u00f3"
} }

View File

@ -0,0 +1,10 @@
{
"config": {
"step": {
"hassio_confirm": {
"description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k Almond pomoc\u00ed hass.io {addon}?",
"title": "Almond prost\u0159ednictv\u00edm dopl\u0148ku Hass.io"
}
}
}
}

View File

@ -6,6 +6,10 @@
"missing_configuration": "Tjek venligst dokumentationen om, hvordan man indstiller Almond." "missing_configuration": "Tjek venligst dokumentationen om, hvordan man indstiller Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Vil du konfigurere Home Assistant til at oprette forbindelse til Almond leveret af Hass.io-tilf\u00f8jelsen: {addon}?",
"title": "Almond via Hass.io-tilf\u00f8jelse"
},
"pick_implementation": { "pick_implementation": {
"title": "V\u00e6lg godkendelsesmetode" "title": "V\u00e6lg godkendelsesmetode"
} }

View File

@ -1,9 +1,9 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_setup": "Sie k\u00f6nnen nur ein Almond-Konto konfigurieren.", "already_setup": "Du kannst nur ein Almond-Konto konfigurieren.",
"cannot_connect": "Verbindung zum Almond-Server nicht m\u00f6glich.", "cannot_connect": "Verbindung zum Almond-Server nicht m\u00f6glich.",
"missing_configuration": "Bitte \u00fcberpr\u00fcfen Sie die Dokumentation zur Einrichtung von Almond." "missing_configuration": "Bitte \u00fcberpr\u00fcfe die Dokumentation zur Einrichtung von Almond."
}, },
"step": { "step": {
"pick_implementation": { "pick_implementation": {

View File

@ -6,6 +6,10 @@
"missing_configuration": "Please check the documentation on how to set up Almond." "missing_configuration": "Please check the documentation on how to set up Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Do you want to configure Home Assistant to connect to Almond provided by the Hass.io add-on: {addon}?",
"title": "Almond via Hass.io add-on"
},
"pick_implementation": { "pick_implementation": {
"title": "Pick Authentication Method" "title": "Pick Authentication Method"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond." "missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Hass.io: {addon} ?",
"title": "Almond a trav\u00e9s del complemento Hass.io"
},
"pick_implementation": { "pick_implementation": {
"title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond." "missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Voulez-vous configurer Home Assistant pour se connecter \u00e0 Almond fourni par le module compl\u00e9mentaire Hass.io: {addon} ?",
"title": "Almonf via le module compl\u00e9mentaire Hass.io"
},
"pick_implementation": { "pick_implementation": {
"title": "S\u00e9lectionner une m\u00e9thode d'authentification" "title": "S\u00e9lectionner une m\u00e9thode d'authentification"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Si prega di controllare la documentazione su come impostare Almond." "missing_configuration": "Si prega di controllare la documentazione su come impostare Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Vuoi configurare Home Assistant a connettersi ad Almond tramite il componente aggiuntivo Hass.io: {addon} ?",
"title": "Almond tramite il componente aggiuntivo di Hass.io"
},
"pick_implementation": { "pick_implementation": {
"title": "Seleziona metodo di autenticazione" "title": "Seleziona metodo di autenticazione"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c Almond \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "Hass.io \uc560\ub4dc\uc628\uc758 Almond"
},
"pick_implementation": { "pick_implementation": {
"title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond." "missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam Almond ze verbannen dee vun der hass.io Erweiderung {addon} bereet gestallt g\u00ebtt?",
"title": "Almond via Hass.io Erweiderung"
},
"pick_implementation": { "pick_implementation": {
"title": "Wielt Authentifikatiouns Method aus" "title": "Wielt Authentifikatiouns Method aus"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond." "missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io add-on: {addon}?",
"title": "Almond via Hass.io add-on"
},
"pick_implementation": { "pick_implementation": {
"title": "Velg autentiseringsmetode" "title": "Velg autentiseringsmetode"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "Prosz\u0119 zapozna\u0107 si\u0119 z dokumentacj\u0105 konfiguracji Almond." "missing_configuration": "Prosz\u0119 zapozna\u0107 si\u0119 z dokumentacj\u0105 konfiguracji Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby \u0142\u0105czy\u0142 si\u0119 z Almond dostarczonym przez dodatek Hass.io: {addon}?",
"title": "Almond poprzez dodatek Hass.io"
},
"pick_implementation": { "pick_implementation": {
"title": "Wybierz metod\u0119 uwierzytelniania" "title": "Wybierz metod\u0119 uwierzytelniania"
} }

View File

@ -6,6 +6,10 @@
"missing_configuration": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 Almond." "missing_configuration": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 Almond."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Almond (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?",
"title": "Almond (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)"
},
"pick_implementation": { "pick_implementation": {
"title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438"
} }

View File

@ -0,0 +1,9 @@
{
"config": {
"step": {
"hassio_confirm": {
"title": "Almond via Hass.io-till\u00e4gget"
}
}
}
}

View File

@ -6,6 +6,10 @@
"missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002" "missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002"
}, },
"step": { "step": {
"hassio_confirm": {
"description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 Almond\uff1f",
"title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 Almond"
},
"pick_implementation": { "pick_implementation": {
"title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f"
} }

View File

@ -3,6 +3,10 @@
"step": { "step": {
"pick_implementation": { "pick_implementation": {
"title": "Pick Authentication Method" "title": "Pick Authentication Method"
},
"hassio_confirm": {
"title": "Almond via Hass.io add-on",
"description": "Do you want to configure Home Assistant to connect to Almond provided by the Hass.io add-on: {addon}?"
} }
}, },
"abort": { "abort": {

View File

@ -30,11 +30,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up Ambient PWS binary sensors based on the old way."""
pass
async def async_setup_entry(hass, entry, async_add_entities): async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Ambient PWS binary sensors based on a config entry.""" """Set up Ambient PWS binary sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]

View File

@ -20,11 +20,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up Ambient PWS sensors based on existing config."""
pass
async def async_setup_entry(hass, entry, async_add_entities): async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Ambient PWS sensors based on a config entry.""" """Set up Ambient PWS sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]

View File

@ -256,7 +256,7 @@ def setup(hass, config):
async_dispatcher_send(hass, service_signal(call.service, entity_id), *args) async_dispatcher_send(hass, service_signal(call.service, entity_id), *args)
for service, params in CAMERA_SERVICES.items(): for service, params in CAMERA_SERVICES.items():
hass.services.async_register(DOMAIN, service, async_service_handler, params[0]) hass.services.register(DOMAIN, service, async_service_handler, params[0])
return True return True

View File

@ -4,5 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/amcrest", "documentation": "https://www.home-assistant.io/integrations/amcrest",
"requirements": ["amcrest==1.5.3"], "requirements": ["amcrest==1.5.3"],
"dependencies": ["ffmpeg"], "dependencies": ["ffmpeg"],
"codeowners": [] "codeowners": ["@pnbruckner"]
} }

View File

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/integrations/androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [ "requirements": [
"adb-shell==0.1.1", "adb-shell==0.1.1",
"androidtv==0.0.38", "androidtv==0.0.39",
"pure-python-adb==0.2.2.dev0" "pure-python-adb==0.2.2.dev0"
], ],
"dependencies": [], "dependencies": [],

View File

@ -26,6 +26,7 @@ from homeassistant.components.media_player.const import (
SUPPORT_TURN_OFF, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_MUTE,
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_STEP,
) )
from homeassistant.const import ( from homeassistant.const import (
@ -59,6 +60,7 @@ SUPPORT_ANDROIDTV = (
| SUPPORT_SELECT_SOURCE | SUPPORT_SELECT_SOURCE
| SUPPORT_STOP | SUPPORT_STOP
| SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_MUTE
| SUPPORT_VOLUME_SET
| SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_STEP
) )
@ -80,6 +82,7 @@ CONF_ADBKEY = "adbkey"
CONF_ADB_SERVER_IP = "adb_server_ip" CONF_ADB_SERVER_IP = "adb_server_ip"
CONF_ADB_SERVER_PORT = "adb_server_port" CONF_ADB_SERVER_PORT = "adb_server_port"
CONF_APPS = "apps" CONF_APPS = "apps"
CONF_EXCLUDE_UNNAMED_APPS = "exclude_unnamed_apps"
CONF_GET_SOURCES = "get_sources" CONF_GET_SOURCES = "get_sources"
CONF_STATE_DETECTION_RULES = "state_detection_rules" CONF_STATE_DETECTION_RULES = "state_detection_rules"
CONF_TURN_ON_COMMAND = "turn_on_command" CONF_TURN_ON_COMMAND = "turn_on_command"
@ -132,12 +135,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_ADB_SERVER_IP): cv.string, vol.Optional(CONF_ADB_SERVER_IP): cv.string,
vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port, vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port,
vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean, vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean,
vol.Optional(CONF_APPS, default=dict()): vol.Schema({cv.string: cv.string}), vol.Optional(CONF_APPS, default=dict()): vol.Schema(
{cv.string: vol.Any(cv.string, None)}
),
vol.Optional(CONF_TURN_ON_COMMAND): cv.string, vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string, vol.Optional(CONF_TURN_OFF_COMMAND): cv.string,
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema( vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
{cv.string: ha_state_detection_rules_validator(vol.Invalid)} {cv.string: ha_state_detection_rules_validator(vol.Invalid)}
), ),
vol.Optional(CONF_EXCLUDE_UNNAMED_APPS, default=False): cv.boolean,
} }
) )
@ -230,6 +236,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
config[CONF_GET_SOURCES], config[CONF_GET_SOURCES],
config.get(CONF_TURN_ON_COMMAND), config.get(CONF_TURN_ON_COMMAND),
config.get(CONF_TURN_OFF_COMMAND), config.get(CONF_TURN_OFF_COMMAND),
config[CONF_EXCLUDE_UNNAMED_APPS],
] ]
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV: if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
@ -365,7 +372,14 @@ class ADBDevice(MediaPlayerDevice):
"""Representation of an Android TV or Fire TV device.""" """Representation of an Android TV or Fire TV device."""
def __init__( def __init__(
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command self,
aftv,
name,
apps,
get_sources,
turn_on_command,
turn_off_command,
exclude_unnamed_apps,
): ):
"""Initialize the Android TV / Fire TV device.""" """Initialize the Android TV / Fire TV device."""
self.aftv = aftv self.aftv = aftv
@ -373,7 +387,7 @@ class ADBDevice(MediaPlayerDevice):
self._app_id_to_name = APPS.copy() self._app_id_to_name = APPS.copy()
self._app_id_to_name.update(apps) self._app_id_to_name.update(apps)
self._app_name_to_id = { self._app_name_to_id = {
value: key for key, value in self._app_id_to_name.items() value: key for key, value in self._app_id_to_name.items() if value
} }
self._get_sources = get_sources self._get_sources = get_sources
self._keys = KEYS self._keys = KEYS
@ -384,12 +398,15 @@ class ADBDevice(MediaPlayerDevice):
self.turn_on_command = turn_on_command self.turn_on_command = turn_on_command
self.turn_off_command = turn_off_command self.turn_off_command = turn_off_command
self._exclude_unnamed_apps = exclude_unnamed_apps
# ADB exceptions to catch # ADB exceptions to catch
if not self.aftv.adb_server_ip: if not self.aftv.adb_server_ip:
# Using "adb_shell" (Python ADB implementation) # Using "adb_shell" (Python ADB implementation)
self.exceptions = ( self.exceptions = (
AttributeError, AttributeError,
BrokenPipeError, BrokenPipeError,
ConnectionResetError,
TypeError, TypeError,
ValueError, ValueError,
InvalidChecksumError, InvalidChecksumError,
@ -558,11 +575,24 @@ class AndroidTVDevice(ADBDevice):
"""Representation of an Android TV device.""" """Representation of an Android TV device."""
def __init__( def __init__(
self, aftv, name, apps, get_sources, turn_on_command, turn_off_command self,
aftv,
name,
apps,
get_sources,
turn_on_command,
turn_off_command,
exclude_unnamed_apps,
): ):
"""Initialize the Android TV device.""" """Initialize the Android TV device."""
super().__init__( super().__init__(
aftv, name, apps, get_sources, turn_on_command, turn_off_command aftv,
name,
apps,
get_sources,
turn_on_command,
turn_off_command,
exclude_unnamed_apps,
) )
self._is_volume_muted = None self._is_volume_muted = None
@ -600,9 +630,13 @@ class AndroidTVDevice(ADBDevice):
self._available = False self._available = False
if running_apps: if running_apps:
self._sources = [ sources = [
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps self._app_id_to_name.get(
app_id, app_id if not self._exclude_unnamed_apps else None
)
for app_id in running_apps
] ]
self._sources = [source for source in sources if source]
else: else:
self._sources = None self._sources = None
@ -631,6 +665,11 @@ class AndroidTVDevice(ADBDevice):
"""Mute the volume.""" """Mute the volume."""
self.aftv.mute_volume() self.aftv.mute_volume()
@adb_decorator()
def set_volume_level(self, volume):
"""Set the volume level."""
self.aftv.set_volume_level(volume)
@adb_decorator() @adb_decorator()
def volume_down(self): def volume_down(self):
"""Send volume down command.""" """Send volume down command."""
@ -670,9 +709,13 @@ class FireTVDevice(ADBDevice):
self._available = False self._available = False
if running_apps: if running_apps:
self._sources = [ sources = [
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps self._app_id_to_name.get(
app_id, app_id if not self._exclude_unnamed_apps else None
)
for app_id in running_apps
] ]
self._sources = [source for source in sources if source]
else: else:
self._sources = None self._sources = None

View File

@ -20,6 +20,7 @@ from homeassistant.const import (
STATE_OFF, STATE_OFF,
STATE_ON, STATE_ON,
) )
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -55,9 +56,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
_LOGGER.info("Provisioning Anthem AVR device at %s:%d", host, port) _LOGGER.info("Provisioning Anthem AVR device at %s:%d", host, port)
@callback
def async_anthemav_update_callback(message): def async_anthemav_update_callback(message):
"""Receive notification from transport that new data exists.""" """Receive notification from transport that new data exists."""
_LOGGER.info("Received update callback from AVR: %s", message) _LOGGER.debug("Received update callback from AVR: %s", message)
hass.async_create_task(device.async_update_ha_state()) hass.async_create_task(device.async_update_ha_state())
avr = await anthemav.Connection.create( avr = await anthemav.Connection.create(

View File

@ -411,6 +411,7 @@ async def async_services_json(hass):
return [{"domain": key, "services": value} for key, value in descriptions.items()] return [{"domain": key, "services": value} for key, value in descriptions.items()]
@ha.callback
def async_events_json(hass): def async_events_json(hass):
"""Generate event data to JSONify.""" """Generate event data to JSONify."""
return [ return [

View File

@ -229,62 +229,42 @@ class AppleTvDevice(MediaPlayerDevice):
self._playing = None self._playing = None
self._power.set_power_on(False) self._power.set_power_on(False)
def async_media_play_pause(self): async def async_media_play_pause(self):
"""Pause media on media player. """Pause media on media player."""
if not self._playing:
This method must be run in the event loop and returns a coroutine. return
"""
if self._playing:
state = self.state state = self.state
if state == STATE_PAUSED: if state == STATE_PAUSED:
return self.atv.remote_control.play() await self.atv.remote_control.play()
if state == STATE_PLAYING: elif state == STATE_PLAYING:
return self.atv.remote_control.pause() await self.atv.remote_control.pause()
def async_media_play(self): async def async_media_play(self):
"""Play media. """Play media."""
This method must be run in the event loop and returns a coroutine.
"""
if self._playing: if self._playing:
return self.atv.remote_control.play() await self.atv.remote_control.play()
def async_media_stop(self): async def async_media_stop(self):
"""Stop the media player. """Stop the media player."""
This method must be run in the event loop and returns a coroutine.
"""
if self._playing: if self._playing:
return self.atv.remote_control.stop() await self.atv.remote_control.stop()
def async_media_pause(self): async def async_media_pause(self):
"""Pause the media player. """Pause the media player."""
This method must be run in the event loop and returns a coroutine.
"""
if self._playing: if self._playing:
return self.atv.remote_control.pause() await self.atv.remote_control.pause()
def async_media_next_track(self): async def async_media_next_track(self):
"""Send next track command. """Send next track command."""
This method must be run in the event loop and returns a coroutine.
"""
if self._playing: if self._playing:
return self.atv.remote_control.next() await self.atv.remote_control.next()
def async_media_previous_track(self): async def async_media_previous_track(self):
"""Send previous track command. """Send previous track command."""
This method must be run in the event loop and returns a coroutine.
"""
if self._playing: if self._playing:
return self.atv.remote_control.previous() await self.atv.remote_control.previous()
def async_media_seek(self, position): async def async_media_seek(self, position):
"""Send seek command. """Send seek command."""
This method must be run in the event loop and returns a coroutine.
"""
if self._playing: if self._playing:
return self.atv.remote_control.set_position(position) await self.atv.remote_control.set_position(position)

View File

@ -61,17 +61,10 @@ class AppleTVRemote(remote.RemoteDevice):
""" """
self._power.set_power_on(False) self._power.set_power_on(False)
def async_send_command(self, command, **kwargs): async def async_send_command(self, command, **kwargs):
"""Send a command to one device. """Send a command to one device."""
This method must be run in the event loop and returns a coroutine.
"""
# Send commands in specified order but schedule only one coroutine
async def _send_commands():
for single_command in command: for single_command in command:
if not hasattr(self._atv.remote_control, single_command): if not hasattr(self._atv.remote_control, single_command):
continue continue
await getattr(self._atv.remote_control, single_command)() await getattr(self._atv.remote_control, single_command)()
return _send_commands()

View File

@ -1,8 +1,8 @@
{ {
"domain": "apprise", "domain": "apprise",
"name": "Apprise", "name": "Apprise",
"documentation": "https://www.home-assistant.io/components/apprise", "documentation": "https://www.home-assistant.io/integrations/apprise",
"requirements": ["apprise==0.8.2"], "requirements": ["apprise==0.8.3"],
"dependencies": [], "dependencies": [],
"codeowners": ["@caronc"] "codeowners": ["@caronc"]
} }

View File

@ -1,4 +1,5 @@
"""Support for the Asterisk Voicemail interface.""" """Support for the Asterisk Voicemail interface."""
from functools import partial
import logging import logging
from asterisk_mbox import ServerError from asterisk_mbox import ServerError
@ -55,7 +56,9 @@ class AsteriskMailbox(Mailbox):
client = self.hass.data[ASTERISK_DOMAIN].client client = self.hass.data[ASTERISK_DOMAIN].client
try: try:
return client.mp3(msgid, sync=True) return await self.hass.async_add_executor_job(
partial(client.mp3, msgid, sync=True)
)
except ServerError as err: except ServerError as err:
raise StreamError(err) raise StreamError(err)
@ -63,9 +66,9 @@ class AsteriskMailbox(Mailbox):
"""Return a list of the current messages.""" """Return a list of the current messages."""
return self.hass.data[ASTERISK_DOMAIN].messages return self.hass.data[ASTERISK_DOMAIN].messages
def async_delete(self, msgid): async def async_delete(self, msgid):
"""Delete the specified messages.""" """Delete the specified messages."""
client = self.hass.data[ASTERISK_DOMAIN].client client = self.hass.data[ASTERISK_DOMAIN].client
_LOGGER.info("Deleting: %s", msgid) _LOGGER.info("Deleting: %s", msgid)
client.delete(msgid) await self.hass.async_add_executor_job(client.delete, msgid)
return True return True

View File

@ -1,6 +1,7 @@
"""Support for aurora forecast data sensor.""" """Support for aurora forecast data sensor."""
from datetime import timedelta from datetime import timedelta
import logging import logging
from math import floor
from aiohttp.hdrs import USER_AGENT from aiohttp.hdrs import USER_AGENT
import requests import requests
@ -99,8 +100,6 @@ class AuroraData:
"""Initialize the data object.""" """Initialize the data object."""
self.latitude = latitude self.latitude = latitude
self.longitude = longitude self.longitude = longitude
self.number_of_latitude_intervals = 513
self.number_of_longitude_intervals = 1024
self.headers = {USER_AGENT: HA_USER_AGENT} self.headers = {USER_AGENT: HA_USER_AGENT}
self.threshold = int(threshold) self.threshold = int(threshold)
self.is_visible = None self.is_visible = None
@ -126,18 +125,22 @@ class AuroraData:
def get_aurora_forecast(self): def get_aurora_forecast(self):
"""Get forecast data and parse for given long/lat.""" """Get forecast data and parse for given long/lat."""
raw_data = requests.get(URL, headers=self.headers, timeout=5).text raw_data = requests.get(URL, headers=self.headers, timeout=5).text
# We discard comment rows (#)
# We split the raw text by line (\n)
# For each line we trim leading spaces and split by spaces
forecast_table = [ forecast_table = [
row.strip(" ").split(" ") row.strip().split()
for row in raw_data.split("\n") for row in raw_data.split("\n")
if not row.startswith("#") if not row.startswith("#")
] ]
# Convert lat and long for data points in table # Convert lat and long for data points in table
converted_latitude = round( # Assumes self.latitude belongs to [-90;90[ (South to North)
(self.latitude / 180) * self.number_of_latitude_intervals # Assumes self.longitude belongs to [-180;180[ (West to East)
) # No assumptions made regarding the number of rows and columns
converted_longitude = round( converted_latitude = floor((self.latitude + 90) * len(forecast_table) / 180)
(self.longitude / 360) * self.number_of_longitude_intervals converted_longitude = floor(
(self.longitude + 180) * len(forecast_table[converted_latitude]) / 360
) )
return forecast_table[converted_latitude][converted_longitude] return forecast_table[converted_latitude][converted_longitude]

View File

@ -25,8 +25,8 @@
}, },
"step": { "step": {
"init": { "init": {
"description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002", "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u96d9\u91cd\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
"title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u5169\u6b65\u9a5f\u9a57\u8b49" "title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u96d9\u91cd\u9a57\u8b49"
} }
}, },
"title": "TOTP" "title": "TOTP"

View File

@ -1,17 +1,19 @@
"""Allow to set up simple automation rules via the config file.""" """Allow to set up simple automation rules via the config file."""
import asyncio
from functools import partial
import importlib import importlib
import logging import logging
from typing import Any, Awaitable, Callable from typing import Any, Awaitable, Callable, List, Optional, Set
import voluptuous as vol import voluptuous as vol
from homeassistant.components import sun
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_NAME, ATTR_NAME,
CONF_DEVICE_ID,
CONF_ENTITY_ID,
CONF_ID, CONF_ID,
CONF_PLATFORM, CONF_PLATFORM,
CONF_ZONE,
EVENT_AUTOMATION_TRIGGERED, EVENT_AUTOMATION_TRIGGERED,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
SERVICE_RELOAD, SERVICE_RELOAD,
@ -20,11 +22,10 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_ON,
STATE_ON, STATE_ON,
) )
from homeassistant.core import Context, CoreState, HomeAssistant from homeassistant.core import Context, CoreState, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import condition, extract_domain_configs, script from homeassistant.helpers import condition, extract_domain_configs, script
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import make_entity_service_schema
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
@ -93,7 +94,9 @@ _TRIGGER_SCHEMA = vol.All(
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA]) _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.Schema( PLATFORM_SCHEMA = vol.All(
cv.deprecated(CONF_HIDE_ENTITY, invalidation_version="0.107"),
vol.Schema(
{ {
# str on purpose # str on purpose
CONF_ID: str, CONF_ID: str,
@ -105,17 +108,9 @@ PLATFORM_SCHEMA = vol.Schema(
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
} }
),
) )
TRIGGER_SERVICE_SCHEMA = make_entity_service_schema(
{
vol.Optional(ATTR_VARIABLES, default={}): dict,
vol.Optional(CONF_SKIP_CONDITION, default=True): bool,
}
)
RELOAD_SERVICE_SCHEMA = vol.Schema({})
@bind_hass @bind_hass
def is_on(hass, entity_id): def is_on(hass, entity_id):
@ -127,48 +122,97 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON) return hass.states.is_state(entity_id, STATE_ON)
@callback
def automations_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]:
"""Return all automations that reference the entity."""
if DOMAIN not in hass.data:
return []
component = hass.data[DOMAIN]
results = []
for automation_entity in component.entities:
if entity_id in automation_entity.referenced_entities:
results.append(automation_entity.entity_id)
return results
@callback
def entities_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]:
"""Return all entities in a scene."""
if DOMAIN not in hass.data:
return []
component = hass.data[DOMAIN]
automation_entity = component.get_entity(entity_id)
if automation_entity is None:
return []
return list(automation_entity.referenced_entities)
@callback
def automations_with_device(hass: HomeAssistant, device_id: str) -> List[str]:
"""Return all automations that reference the device."""
if DOMAIN not in hass.data:
return []
component = hass.data[DOMAIN]
results = []
for automation_entity in component.entities:
if device_id in automation_entity.referenced_devices:
results.append(automation_entity.entity_id)
return results
@callback
def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]:
"""Return all devices in a scene."""
if DOMAIN not in hass.data:
return []
component = hass.data[DOMAIN]
automation_entity = component.get_entity(entity_id)
if automation_entity is None:
return []
return list(automation_entity.referenced_devices)
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the automation.""" """Set up the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass) hass.data[DOMAIN] = component = EntityComponent(_LOGGER, DOMAIN, hass)
await _async_process_config(hass, config, component) await _async_process_config(hass, config, component)
async def trigger_service_handler(service_call): async def trigger_service_handler(entity, service_call):
"""Handle automation triggers.""" """Handle automation triggers."""
tasks = [] await entity.async_trigger(
for entity in await component.async_extract_from_service(service_call):
tasks.append(
entity.async_trigger(
service_call.data[ATTR_VARIABLES], service_call.data[ATTR_VARIABLES],
skip_condition=service_call.data[CONF_SKIP_CONDITION], skip_condition=service_call.data[CONF_SKIP_CONDITION],
context=service_call.context, context=service_call.context,
) )
component.async_register_entity_service(
SERVICE_TRIGGER,
{
vol.Optional(ATTR_VARIABLES, default={}): dict,
vol.Optional(CONF_SKIP_CONDITION, default=True): bool,
},
trigger_service_handler,
) )
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
if tasks: component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on")
await asyncio.wait(tasks) component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
async def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
tasks = []
method = f"async_{service_call.service}"
for entity in await component.async_extract_from_service(service_call):
tasks.append(getattr(entity, method)())
if tasks:
await asyncio.wait(tasks)
async def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
tasks = []
for entity in await component.async_extract_from_service(service_call):
if entity.is_on:
tasks.append(entity.async_turn_off())
else:
tasks.append(entity.async_turn_on())
if tasks:
await asyncio.wait(tasks)
async def reload_service_handler(service_call): async def reload_service_handler(service_call):
"""Remove all automations and load new ones from config.""" """Remove all automations and load new ones from config."""
@ -177,31 +221,8 @@ async def async_setup(hass, config):
return return
await _async_process_config(hass, conf, component) await _async_process_config(hass, conf, component)
hass.services.async_register(
DOMAIN, SERVICE_TRIGGER, trigger_service_handler, schema=TRIGGER_SERVICE_SCHEMA
)
async_register_admin_service( async_register_admin_service(
hass, hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}),
DOMAIN,
SERVICE_RELOAD,
reload_service_handler,
schema=RELOAD_SERVICE_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SERVICE_TOGGLE,
toggle_service_handler,
schema=make_entity_service_schema({}),
)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
hass.services.async_register(
DOMAIN,
service,
turn_onoff_service_handler,
schema=make_entity_service_schema({}),
) )
return True return True
@ -214,29 +235,36 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
self, self,
automation_id, automation_id,
name, name,
async_attach_triggers, trigger_config,
cond_func, cond_func,
async_action, action_script,
hidden, hidden,
initial_state, initial_state,
): ):
"""Initialize an automation entity.""" """Initialize an automation entity."""
self._id = automation_id self._id = automation_id
self._name = name self._name = name
self._async_attach_triggers = async_attach_triggers self._trigger_config = trigger_config
self._async_detach_triggers = None self._async_detach_triggers = None
self._cond_func = cond_func self._cond_func = cond_func
self._async_action = async_action self.action_script = action_script
self._last_triggered = None self._last_triggered = None
self._hidden = hidden self._hidden = hidden
self._initial_state = initial_state self._initial_state = initial_state
self._is_enabled = False self._is_enabled = False
self._referenced_entities: Optional[Set[str]] = None
self._referenced_devices: Optional[Set[str]] = None
@property @property
def name(self): def name(self):
"""Name of the automation.""" """Name of the automation."""
return self._name return self._name
@property
def unique_id(self):
"""Return unique ID."""
return self._id
@property @property
def should_poll(self): def should_poll(self):
"""No polling needed for automation entities.""" """No polling needed for automation entities."""
@ -257,6 +285,45 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
"""Return True if entity is on.""" """Return True if entity is on."""
return self._async_detach_triggers is not None or self._is_enabled return self._async_detach_triggers is not None or self._is_enabled
@property
def referenced_devices(self):
"""Return a set of referenced devices."""
if self._referenced_devices is not None:
return self._referenced_devices
referenced = self.action_script.referenced_devices
if self._cond_func is not None:
for conf in self._cond_func.config:
referenced |= condition.async_extract_devices(conf)
for conf in self._trigger_config:
device = _trigger_extract_device(conf)
if device is not None:
referenced.add(device)
self._referenced_devices = referenced
return referenced
@property
def referenced_entities(self):
"""Return a set of referenced entities."""
if self._referenced_entities is not None:
return self._referenced_entities
referenced = self.action_script.referenced_entities
if self._cond_func is not None:
for conf in self._cond_func.config:
referenced |= condition.async_extract_entities(conf)
for conf in self._trigger_config:
for entity_id in _trigger_extract_entities(conf):
referenced.add(entity_id)
self._referenced_entities = referenced
return referenced
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state.""" """Startup with initial state or previous state."""
await super().async_added_to_hass() await super().async_added_to_hass()
@ -307,7 +374,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
This method is a coroutine. This method is a coroutine.
""" """
if not skip_condition and not self._cond_func(variables): if (
not skip_condition
and self._cond_func is not None
and not self._cond_func(variables)
):
return return
# Create a new context referring to the old context. # Create a new context referring to the old context.
@ -320,7 +391,16 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
{ATTR_NAME: self._name, ATTR_ENTITY_ID: self.entity_id}, {ATTR_NAME: self._name, ATTR_ENTITY_ID: self.entity_id},
context=trigger_context, context=trigger_context,
) )
await self._async_action(self.entity_id, variables, trigger_context)
_LOGGER.info("Executing %s", self._name)
try:
await self.action_script.async_run(variables, trigger_context)
except Exception as err: # pylint: disable=broad-except
self.action_script.async_log_exception(
_LOGGER, f"Error while executing automation {self.entity_id}", err
)
self._last_triggered = utcnow() self._last_triggered = utcnow()
await self.async_update_ha_state() await self.async_update_ha_state()
@ -341,9 +421,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
# HomeAssistant is starting up # HomeAssistant is starting up
if self.hass.state != CoreState.not_running: if self.hass.state != CoreState.not_running:
self._async_detach_triggers = await self._async_attach_triggers( self._async_detach_triggers = await self._async_attach_triggers()
self.async_trigger
)
self.async_write_ha_state() self.async_write_ha_state()
return return
@ -353,9 +431,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
if not self._is_enabled or self._async_detach_triggers is not None: if not self._is_enabled or self._async_detach_triggers is not None:
return return
self._async_detach_triggers = await self._async_attach_triggers( self._async_detach_triggers = await self._async_attach_triggers()
self.async_trigger
)
self.hass.bus.async_listen_once( self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_enable_automation EVENT_HOMEASSISTANT_START, async_enable_automation
@ -375,6 +451,38 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
self.async_write_ha_state() self.async_write_ha_state()
async def _async_attach_triggers(self):
"""Set up the triggers."""
removes = []
info = {"name": self._name}
for conf in self._trigger_config:
platform = importlib.import_module(
".{}".format(conf[CONF_PLATFORM]), __name__
)
remove = await platform.async_attach_trigger(
self.hass, conf, self.async_trigger, info
)
if not remove:
_LOGGER.error("Error setting up trigger %s", self._name)
continue
_LOGGER.info("Initialized trigger %s", self._name)
removes.append(remove)
if not removes:
return None
@callback
def remove_triggers():
"""Remove attached triggers."""
for remove in removes:
remove()
return remove_triggers
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return automation attributes.""" """Return automation attributes."""
@ -401,7 +509,7 @@ async def _async_process_config(hass, config, component):
hidden = config_block[CONF_HIDE_ENTITY] hidden = config_block[CONF_HIDE_ENTITY]
initial_state = config_block.get(CONF_INITIAL_STATE) initial_state = config_block.get(CONF_INITIAL_STATE)
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) action_script = script.Script(hass, config_block.get(CONF_ACTION, {}), name)
if CONF_CONDITION in config_block: if CONF_CONDITION in config_block:
cond_func = await _async_process_if(hass, config, config_block) cond_func = await _async_process_if(hass, config, config_block)
@ -409,24 +517,14 @@ async def _async_process_config(hass, config, component):
if cond_func is None: if cond_func is None:
continue continue
else: else:
cond_func = None
def cond_func(variables):
"""Condition will always pass."""
return True
async_attach_triggers = partial(
_async_process_trigger,
hass,
config,
config_block.get(CONF_TRIGGER, []),
name,
)
entity = AutomationEntity( entity = AutomationEntity(
automation_id, automation_id,
name, name,
async_attach_triggers, config_block[CONF_TRIGGER],
cond_func, cond_func,
action, action_script,
hidden, hidden,
initial_state, initial_state,
) )
@ -437,27 +535,9 @@ async def _async_process_config(hass, config, component):
await component.async_add_entities(entities) await component.async_add_entities(entities)
def _async_get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
async def action(entity_id, variables, context):
"""Execute an action."""
_LOGGER.info("Executing %s", name)
try:
await script_obj.async_run(variables, context)
except Exception as err: # pylint: disable=broad-except
script_obj.async_log_exception(
_LOGGER, f"Error while executing automation {entity_id}", err
)
return action
async def _async_process_if(hass, config, p_config): async def _async_process_if(hass, config, p_config):
"""Process if checks.""" """Process if checks."""
if_configs = p_config.get(CONF_CONDITION) if_configs = p_config[CONF_CONDITION]
checks = [] checks = []
for if_config in if_configs: for if_config in if_configs:
@ -471,35 +551,33 @@ async def _async_process_if(hass, config, p_config):
"""AND all conditions.""" """AND all conditions."""
return all(check(hass, variables) for check in checks) return all(check(hass, variables) for check in checks)
if_action.config = if_configs
return if_action return if_action
async def _async_process_trigger(hass, config, trigger_configs, name, action): @callback
"""Set up the triggers. def _trigger_extract_device(trigger_conf: dict) -> Optional[str]:
"""Extract devices from a trigger config."""
This method is a coroutine. if trigger_conf[CONF_PLATFORM] != "device":
"""
removes = []
info = {"name": name}
for conf in trigger_configs:
platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__)
remove = await platform.async_attach_trigger(hass, conf, action, info)
if not remove:
_LOGGER.error("Error setting up trigger %s", name)
continue
_LOGGER.info("Initialized trigger %s", name)
removes.append(remove)
if not removes:
return None return None
def remove_triggers(): return trigger_conf[CONF_DEVICE_ID]
"""Remove attached triggers."""
for remove in removes:
remove()
return remove_triggers
@callback
def _trigger_extract_entities(trigger_conf: dict) -> List[str]:
"""Extract entities from a trigger config."""
if trigger_conf[CONF_PLATFORM] in ("state", "numeric_state"):
return trigger_conf[CONF_ENTITY_ID]
if trigger_conf[CONF_PLATFORM] == "zone":
return trigger_conf[CONF_ENTITY_ID] + [trigger_conf[CONF_ZONE]]
if trigger_conf[CONF_PLATFORM] == "geo_location":
return [trigger_conf[CONF_ZONE]]
if trigger_conf[CONF_PLATFORM] == "sun":
return [sun.ENTITY_ID]
return []

View File

@ -92,6 +92,7 @@ async def async_attach_trigger(hass, config, action, automation_info):
hass.data["litejet_system"].on_switch_pressed(number, pressed) hass.data["litejet_system"].on_switch_pressed(number, pressed)
hass.data["litejet_system"].on_switch_released(number, released) hass.data["litejet_system"].on_switch_released(number, released)
@callback
def async_remove(): def async_remove():
"""Remove all subscriptions used for this trigger.""" """Remove all subscriptions used for this trigger."""
return return

View File

@ -2,7 +2,7 @@
"domain": "aws", "domain": "aws",
"name": "Amazon Web Services (AWS)", "name": "Amazon Web Services (AWS)",
"documentation": "https://www.home-assistant.io/integrations/aws", "documentation": "https://www.home-assistant.io/integrations/aws",
"requirements": ["aiobotocore==0.10.4"], "requirements": ["aiobotocore==0.11.1"],
"dependencies": [], "dependencies": [],
"codeowners": ["@awarecan", "@robbiet480"] "codeowners": ["@awarecan", "@robbiet480"]
} }

View File

@ -4,7 +4,8 @@
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"bad_config_file": "Mauvaises donn\u00e9es du fichier de configuration", "bad_config_file": "Mauvaises donn\u00e9es du fichier de configuration",
"link_local_address": "Les adresses locales ne sont pas prises en charge", "link_local_address": "Les adresses locales ne sont pas prises en charge",
"not_axis_device": "L'appareil d\u00e9couvert n'est pas un appareil Axis" "not_axis_device": "L'appareil d\u00e9couvert n'est pas un appareil Axis",
"updated_configuration": "Mise \u00e0 jour de la configuration du dispositif avec la nouvelle adresse de l'h\u00f4te"
}, },
"error": { "error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",

View File

@ -59,11 +59,11 @@
"moving": "{entity_name} ha iniziato a muoversi", "moving": "{entity_name} ha iniziato a muoversi",
"no_gas": "{entity_name} ha smesso la rilevazione di gas", "no_gas": "{entity_name} ha smesso la rilevazione di gas",
"no_light": "{entity_name} smesso il rilevamento di luce", "no_light": "{entity_name} smesso il rilevamento di luce",
"no_motion": "{nome_entit\u00e0} ha smesso di rilevare il movimento", "no_motion": "{entity_name} ha smesso di rilevare il movimento",
"no_problem": "{nome_entit\u00e0} ha smesso di rilevare un problema", "no_problem": "{entity_name} ha smesso di rilevare un problema",
"no_smoke": "{entity_name} ha smesso la rilevazione di fumo", "no_smoke": "{entity_name} ha smesso la rilevazione di fumo",
"no_sound": "{nome_entit\u00e0} ha smesso di rilevare il suono", "no_sound": "{entity_name} ha smesso di rilevare il suono",
"no_vibration": "{nome_entit\u00e0} ha smesso di rilevare le vibrazioni", "no_vibration": "{entity_name} ha smesso di rilevare le vibrazioni",
"not_bat_low": "{entity_name} batteria normale", "not_bat_low": "{entity_name} batteria normale",
"not_cold": "{entity_name} non \u00e8 diventato freddo", "not_cold": "{entity_name} non \u00e8 diventato freddo",
"not_connected": "{entity_name} \u00e8 disconnesso", "not_connected": "{entity_name} \u00e8 disconnesso",

View File

@ -5,7 +5,7 @@ import voluptuous as vol
from homeassistant.components.device_automation.const import CONF_IS_OFF, CONF_IS_ON from homeassistant.components.device_automation.const import CONF_IS_OFF, CONF_IS_ON
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers.entity_registry import ( from homeassistant.helpers.entity_registry import (
async_entries_for_device, async_entries_for_device,
@ -232,6 +232,7 @@ async def async_get_conditions(
return conditions return conditions
@callback
def async_condition_from_config( def async_condition_from_config(
config: ConfigType, config_validation: bool config: ConfigType, config_validation: bool
) -> condition.ConditionCheckerType: ) -> condition.ConditionCheckerType:

View File

@ -10,6 +10,7 @@ import socket
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -64,11 +65,13 @@ SERVICE_SEND_SCHEMA = vol.Schema(
SERVICE_LEARN_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string}) SERVICE_LEARN_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})
@callback
def async_setup_service(hass, host, device): def async_setup_service(hass, host, device):
"""Register a device for given host for use in services.""" """Register a device for given host for use in services."""
hass.data.setdefault(DOMAIN, {})[host] = device hass.data.setdefault(DOMAIN, {})[host] = device
if not hass.services.has_service(DOMAIN, SERVICE_LEARN): if hass.services.has_service(DOMAIN, SERVICE_LEARN):
return
async def _learn_command(call): async def _learn_command(call):
"""Learn a packet from remote.""" """Learn a packet from remote."""
@ -107,8 +110,6 @@ def async_setup_service(hass, host, device):
DOMAIN, SERVICE_LEARN, _learn_command, schema=SERVICE_LEARN_SCHEMA DOMAIN, SERVICE_LEARN, _learn_command, schema=SERVICE_LEARN_SCHEMA
) )
if not hass.services.has_service(DOMAIN, SERVICE_SEND):
async def _send_packet(call): async def _send_packet(call):
"""Send a packet.""" """Send a packet."""
device = hass.data[DOMAIN][call.data[CONF_HOST]] device = hass.data[DOMAIN][call.data[CONF_HOST]]

View File

@ -0,0 +1,14 @@
{
"config": {
"flow_title": "Tisk\u00e1rna Brother: {model} {serial_number}",
"step": {
"zeroconf_confirm": {
"data": {
"type": "Typ tisk\u00e1rny"
},
"description": "Chcete p\u0159idat tisk\u00e1rnu Brother {model} se s\u00e9riov\u00fdm \u010d\u00edslem \"{serial_number}\" do Home Assistant?",
"title": "Objeven\u00e1 tisk\u00e1rna Brother"
}
}
}
}

View File

@ -9,6 +9,7 @@
"snmp_error": "SNMP-server er sl\u00e5et fra, eller printeren underst\u00f8ttes ikke.", "snmp_error": "SNMP-server er sl\u00e5et fra, eller printeren underst\u00f8ttes ikke.",
"wrong_host": "Ugyldigt v\u00e6rtsnavn eller IP-adresse." "wrong_host": "Ugyldigt v\u00e6rtsnavn eller IP-adresse."
}, },
"flow_title": "Brother-printer: {model} {serial_number}",
"step": { "step": {
"user": { "user": {
"data": { "data": {
@ -17,6 +18,13 @@
}, },
"description": "Konfigurer Brother-printerintegration. Hvis du har problemer med konfiguration, kan du g\u00e5 til: https://www.home-assistant.io/integrations/brother", "description": "Konfigurer Brother-printerintegration. Hvis du har problemer med konfiguration, kan du g\u00e5 til: https://www.home-assistant.io/integrations/brother",
"title": "Brother-printer" "title": "Brother-printer"
},
"zeroconf_confirm": {
"data": {
"type": "Type af printer"
},
"description": "Vil du tilf\u00f8je Brother-printeren {model} med serienummeret `{serial_number}` til Home Assistant?",
"title": "Fandt Brother-printer"
} }
}, },
"title": "Brother-printer" "title": "Brother-printer"

View File

@ -0,0 +1,24 @@
{
"config": {
"abort": {
"already_configured": "Dieser Drucker ist bereits konfiguriert",
"unsupported_model": "Dieses Druckermodell wird nicht unterst\u00fctzt."
},
"error": {
"connection_error": "Verbindungsfehler",
"snmp_error": "SNMP-Server deaktiviert oder Drucker nicht unterst\u00fctzt.",
"wrong_host": " Ung\u00fcltiger Hostname oder IP-Adresse"
},
"step": {
"user": {
"data": {
"host": "Drucker Hostname oder IP-Adresse",
"type": "Typ des Druckers"
},
"description": "Einrichten der Brother-Drucker-Integration. Wenn Du Probleme mit der Konfiguration hast, gehe zu: https://www.home-assistant.io/integrations/brother",
"title": "Brother Drucker"
}
},
"title": "Brother Drucker"
}
}

View File

@ -9,6 +9,7 @@
"snmp_error": "SNMP server turned off or printer not supported.", "snmp_error": "SNMP server turned off or printer not supported.",
"wrong_host": "Invalid hostname or IP address." "wrong_host": "Invalid hostname or IP address."
}, },
"flow_title": "Brother Printer: {model} {serial_number}",
"step": { "step": {
"user": { "user": {
"data": { "data": {
@ -17,6 +18,13 @@
}, },
"description": "Set up Brother printer integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/brother", "description": "Set up Brother printer integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/brother",
"title": "Brother Printer" "title": "Brother Printer"
},
"zeroconf_confirm": {
"data": {
"type": "Type of the printer"
},
"description": "Do you want to add the Brother Printer {model} with serial number `{serial_number}` to Home Assistant?",
"title": "Discovered Brother Printer"
} }
}, },
"title": "Brother Printer" "title": "Brother Printer"

View File

@ -9,6 +9,7 @@
"snmp_error": "El servidor SNMP est\u00e1 apagado o la impresora no es compatible.", "snmp_error": "El servidor SNMP est\u00e1 apagado o la impresora no es compatible.",
"wrong_host": "Nombre del host o direcci\u00f3n IP no v\u00e1lidos." "wrong_host": "Nombre del host o direcci\u00f3n IP no v\u00e1lidos."
}, },
"flow_title": "Impresora Brother: {model} {serial_number}",
"step": { "step": {
"user": { "user": {
"data": { "data": {
@ -17,6 +18,13 @@
}, },
"description": "Configure la integraci\u00f3n de impresoras Brother. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/brother", "description": "Configure la integraci\u00f3n de impresoras Brother. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/brother",
"title": "Impresora Brother" "title": "Impresora Brother"
},
"zeroconf_confirm": {
"data": {
"type": "Tipo de impresora"
},
"description": "\u00bfQuiere a\u00f1adir la Impresora Brother {model} con el n\u00famero de serie `{serial_number}` a Home Assistant?",
"title": "Impresora Brother encontrada"
} }
}, },
"title": "Impresora Brother" "title": "Impresora Brother"

View File

@ -0,0 +1,29 @@
{
"config": {
"abort": {
"already_configured": "Cette imprimante est d\u00e9j\u00e0 configur\u00e9e.",
"unsupported_model": "Ce mod\u00e8le d'imprimante n'est pas pris en charge."
},
"error": {
"connection_error": "Erreur de connexion.",
"snmp_error": "Serveur SNMP d\u00e9sactiv\u00e9 ou imprimante non prise en charge.",
"wrong_host": "Nom d'h\u00f4te ou adresse IP invalide."
},
"step": {
"user": {
"data": {
"host": "Nom d'h\u00f4te ou adresse IP de l'imprimante",
"type": "Type d'imprimante"
},
"description": "Configurez l'int\u00e9gration de l'imprimante Brother. Si vous avez des probl\u00e8mes avec la configuration, allez \u00e0 : https://www.home-assistant.io/integrations/brother",
"title": "Imprimante Brother"
},
"zeroconf_confirm": {
"data": {
"type": "Type d'imprimante"
}
}
},
"title": "Imprimante Brother"
}
}

View File

@ -0,0 +1,15 @@
{
"config": {
"flow_title": "Brother nyomtat\u00f3: {model} {serial_number}",
"step": {
"zeroconf_confirm": {
"data": {
"type": "A nyomtat\u00f3 t\u00edpusa"
},
"description": "Hozz\u00e1 akarja adni a {model} Brother nyomtat\u00f3t, amelynek sorsz\u00e1ma: {serial_number} `, a Home Assistant-hoz?",
"title": "Felfedezett Brother nyomtat\u00f3"
}
},
"title": "Brother nyomtat\u00f3"
}
}

View File

@ -9,6 +9,7 @@
"snmp_error": "Server SNMP spento o stampante non supportata.", "snmp_error": "Server SNMP spento o stampante non supportata.",
"wrong_host": "Nome host o indirizzo IP non valido." "wrong_host": "Nome host o indirizzo IP non valido."
}, },
"flow_title": "Stampante Brother: {model} {serial_number}",
"step": { "step": {
"user": { "user": {
"data": { "data": {
@ -17,6 +18,13 @@
}, },
"description": "Configurare l'integrazione della stampante Brother. In caso di problemi con la configurazione, visitare: https://www.home-assistant.io/integrations/brother", "description": "Configurare l'integrazione della stampante Brother. In caso di problemi con la configurazione, visitare: https://www.home-assistant.io/integrations/brother",
"title": "Stampante Brother" "title": "Stampante Brother"
},
"zeroconf_confirm": {
"data": {
"type": "Tipo di stampante"
},
"description": "Vuoi aggiungere la stampante Brother {model} con il numero seriale `{serial_number}` a Home Assistant?",
"title": "Trovata stampante Brother"
} }
}, },
"title": "Stampante Brother" "title": "Stampante Brother"

View File

@ -1,6 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\uc774 \ud504\ub9b0\ud130\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"unsupported_model": "\uc774 \ud504\ub9b0\ud130 \ubaa8\ub378\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." "unsupported_model": "\uc774 \ud504\ub9b0\ud130 \ubaa8\ub378\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4."
}, },
"error": { "error": {
@ -8,6 +9,7 @@
"snmp_error": "SNMP \uc11c\ubc84\uac00 \uaebc\uc838 \uc788\uac70\ub098 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud504\ub9b0\ud130\uc785\ub2c8\ub2e4.", "snmp_error": "SNMP \uc11c\ubc84\uac00 \uaebc\uc838 \uc788\uac70\ub098 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud504\ub9b0\ud130\uc785\ub2c8\ub2e4.",
"wrong_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4." "wrong_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4."
}, },
"flow_title": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130: {model} {serial_number}",
"step": { "step": {
"user": { "user": {
"data": { "data": {
@ -16,6 +18,13 @@
}, },
"description": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00\uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/brother \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", "description": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00\uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/brother \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694",
"title": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130" "title": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130"
},
"zeroconf_confirm": {
"data": {
"type": "\ud504\ub9b0\ud130\uc758 \uc885\ub958"
},
"description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \ub85c \ube0c\ub77c\ub354 \ud504\ub9b0\ud130 {model} \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "\ubc1c\uacac\ub41c \ube0c\ub77c\ub354 \ud504\ub9b0\ud130"
} }
}, },
"title": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130" "title": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130"

Some files were not shown because too many files have changed in this diff Show More