mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 08:47:10 +00:00
commit
b2cd6707b6
35
.coveragerc
35
.coveragerc
@ -6,7 +6,6 @@ omit =
|
||||
homeassistant/helpers/signal.py
|
||||
homeassistant/helpers/typing.py
|
||||
homeassistant/scripts/*.py
|
||||
homeassistant/util/async.py
|
||||
|
||||
# omit pieces of code that rely on external devices being present
|
||||
homeassistant/components/abode/__init__.py
|
||||
@ -32,7 +31,6 @@ omit =
|
||||
homeassistant/components/airly/const.py
|
||||
homeassistant/components/airvisual/sensor.py
|
||||
homeassistant/components/aladdin_connect/cover.py
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py
|
||||
homeassistant/components/alarmdecoder/*
|
||||
homeassistant/components/alarmdotcom/alarm_control_panel.py
|
||||
homeassistant/components/alpha_vantage/sensor.py
|
||||
@ -116,7 +114,6 @@ omit =
|
||||
homeassistant/components/cisco_ios/device_tracker.py
|
||||
homeassistant/components/cisco_mobility_express/device_tracker.py
|
||||
homeassistant/components/cisco_webex_teams/notify.py
|
||||
homeassistant/components/ciscospark/notify.py
|
||||
homeassistant/components/citybikes/sensor.py
|
||||
homeassistant/components/clementine/media_player.py
|
||||
homeassistant/components/clickatell/notify.py
|
||||
@ -256,13 +253,15 @@ omit =
|
||||
homeassistant/components/frontier_silicon/media_player.py
|
||||
homeassistant/components/futurenow/light.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/geniushub/*
|
||||
homeassistant/components/gearbest/sensor.py
|
||||
homeassistant/components/geizhals/sensor.py
|
||||
homeassistant/components/gios/__init__.py
|
||||
homeassistant/components/gios/air_quality.py
|
||||
homeassistant/components/gios/consts.py
|
||||
homeassistant/components/github/sensor.py
|
||||
homeassistant/components/gitlab_ci/sensor.py
|
||||
homeassistant/components/gitter/sensor.py
|
||||
@ -307,7 +306,6 @@ omit =
|
||||
homeassistant/components/homematic/notify.py
|
||||
homeassistant/components/homeworks/*
|
||||
homeassistant/components/honeywell/climate.py
|
||||
homeassistant/components/hook/switch.py
|
||||
homeassistant/components/horizon/media_player.py
|
||||
homeassistant/components/hp_ilo/sensor.py
|
||||
homeassistant/components/htu21d/sensor.py
|
||||
@ -324,6 +322,7 @@ omit =
|
||||
homeassistant/components/iaqualink/sensor.py
|
||||
homeassistant/components/iaqualink/switch.py
|
||||
homeassistant/components/icloud/__init__.py
|
||||
homeassistant/components/icloud/account.py
|
||||
homeassistant/components/icloud/device_tracker.py
|
||||
homeassistant/components/icloud/sensor.py
|
||||
homeassistant/components/izone/climate.py
|
||||
@ -421,7 +420,8 @@ omit =
|
||||
homeassistant/components/metoffice/weather.py
|
||||
homeassistant/components/microsoft/tts.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/const.py
|
||||
homeassistant/components/minio/*
|
||||
@ -455,8 +455,13 @@ omit =
|
||||
homeassistant/components/nederlandse_spoorwegen/sensor.py
|
||||
homeassistant/components/nello/lock.py
|
||||
homeassistant/components/nest/*
|
||||
homeassistant/components/netatmo/*
|
||||
homeassistant/components/netatmo_public/sensor.py
|
||||
homeassistant/components/netatmo/__init__.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/netgear/device_tracker.py
|
||||
homeassistant/components/netgear_lte/*
|
||||
@ -504,13 +509,13 @@ omit =
|
||||
homeassistant/components/openuv/sensor.py
|
||||
homeassistant/components/openweathermap/sensor.py
|
||||
homeassistant/components/openweathermap/weather.py
|
||||
homeassistant/components/opnsense/*
|
||||
homeassistant/components/opple/light.py
|
||||
homeassistant/components/orangepi_gpio/*
|
||||
homeassistant/components/oru/*
|
||||
homeassistant/components/orvibo/switch.py
|
||||
homeassistant/components/osramlightify/light.py
|
||||
homeassistant/components/otp/sensor.py
|
||||
homeassistant/components/owlet/*
|
||||
homeassistant/components/panasonic_bluray/media_player.py
|
||||
homeassistant/components/panasonic_viera/media_player.py
|
||||
homeassistant/components/pandora/media_player.py
|
||||
@ -530,12 +535,10 @@ omit =
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/sensor.py
|
||||
homeassistant/components/plex/server.py
|
||||
homeassistant/components/plex/websockets.py
|
||||
homeassistant/components/plugwise/*
|
||||
homeassistant/components/plum_lightpad/*
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
homeassistant/components/point/*
|
||||
homeassistant/components/postnl/sensor.py
|
||||
homeassistant/components/prezzibenzina/sensor.py
|
||||
homeassistant/components/proliphix/climate.py
|
||||
homeassistant/components/prometheus/*
|
||||
@ -637,6 +640,7 @@ omit =
|
||||
homeassistant/components/smappee/*
|
||||
homeassistant/components/smarty/*
|
||||
homeassistant/components/smarthab/*
|
||||
homeassistant/components/sms/*
|
||||
homeassistant/components/smtp/notify.py
|
||||
homeassistant/components/snapcast/media_player.py
|
||||
homeassistant/components/snmp/*
|
||||
@ -659,6 +663,7 @@ omit =
|
||||
homeassistant/components/speedtestdotnet/*
|
||||
homeassistant/components/spider/*
|
||||
homeassistant/components/spotcrime/sensor.py
|
||||
homeassistant/components/spotify/__init__.py
|
||||
homeassistant/components/spotify/media_player.py
|
||||
homeassistant/components/squeezebox/*
|
||||
homeassistant/components/starline/*
|
||||
@ -724,7 +729,6 @@ omit =
|
||||
homeassistant/components/torque/sensor.py
|
||||
homeassistant/components/totalconnect/*
|
||||
homeassistant/components/touchline/climate.py
|
||||
homeassistant/components/tplink/device_tracker.py
|
||||
homeassistant/components/tplink/switch.py
|
||||
homeassistant/components/tplink_lte/*
|
||||
homeassistant/components/traccar/device_tracker.py
|
||||
@ -749,7 +753,6 @@ omit =
|
||||
homeassistant/components/twitch/sensor.py
|
||||
homeassistant/components/twitter/notify.py
|
||||
homeassistant/components/ubee/device_tracker.py
|
||||
homeassistant/components/uber/sensor.py
|
||||
homeassistant/components/ubus/device_tracker.py
|
||||
homeassistant/components/ue_smart_radio/media_player.py
|
||||
homeassistant/components/unifiled/*
|
||||
@ -779,7 +782,9 @@ omit =
|
||||
homeassistant/components/viaggiatreno/sensor.py
|
||||
homeassistant/components/vicare/*
|
||||
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_telnet/media_player.py
|
||||
homeassistant/components/volkszaehler/sensor.py
|
||||
@ -825,7 +830,6 @@ omit =
|
||||
homeassistant/components/zestimate/sensor.py
|
||||
homeassistant/components/zha/__init__.py
|
||||
homeassistant/components/zha/api.py
|
||||
homeassistant/components/zha/const.py
|
||||
homeassistant/components/zha/core/channels/*
|
||||
homeassistant/components/zha/core/const.py
|
||||
homeassistant/components/zha/core/device.py
|
||||
@ -833,7 +837,6 @@ omit =
|
||||
homeassistant/components/zha/core/helpers.py
|
||||
homeassistant/components/zha/core/patches.py
|
||||
homeassistant/components/zha/core/registries.py
|
||||
homeassistant/components/zha/device_entity.py
|
||||
homeassistant/components/zha/entity.py
|
||||
homeassistant/components/zha/light.py
|
||||
homeassistant/components/zha/sensor.py
|
||||
|
48
.github/ISSUE_TEMPLATE.md
vendored
48
.github/ISSUE_TEMPLATE.md
vendored
@ -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
53
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
Normal 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
|
||||
|
52
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
52
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@ -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
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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!
|
112
.github/PULL_REQUEST_TEMPLATE.md
vendored
112
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,35 +1,109 @@
|
||||
## Breaking Change:
|
||||
|
||||
<!-- What is breaking and why we have to break it. Remove this section only if it was NOT a breaking change. -->
|
||||
|
||||
## Description:
|
||||
<!--
|
||||
You are amazing! Thanks for contributing to our project!
|
||||
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
|
||||
-->
|
||||
## 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
|
||||
# Example configuration.yaml
|
||||
|
||||
```
|
||||
|
||||
## Checklist:
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] Local tests pass with `tox`. **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]
|
||||
## Additional information
|
||||
<!--
|
||||
Details are important, and help maintainers processing your PR.
|
||||
Please be sure to fill out additional details, if applicable.
|
||||
-->
|
||||
|
||||
- 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:
|
||||
- [ ] 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:
|
||||
- [ ] [_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:
|
||||
- [ ] Tests have been added to verify that the new code works.
|
||||
- [ ] The [manifest file][manifest-docs] has all fields filled out correctly.
|
||||
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
|
||||
[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
|
||||
|
@ -1,2 +0,0 @@
|
||||
python:
|
||||
enabled: true
|
@ -24,7 +24,7 @@ repos:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==5.0.1
|
||||
- pydocstyle==5.0.2
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.6.2
|
||||
|
@ -20,7 +20,7 @@ repos:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==5.0.1
|
||||
- pydocstyle==5.0.2
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.6.2
|
||||
|
23
CODEOWNERS
23
CODEOWNERS
@ -23,6 +23,7 @@ homeassistant/components/alpha_vantage/* @fabaff
|
||||
homeassistant/components/amazon_polly/* @robbiet480
|
||||
homeassistant/components/ambiclimate/* @danielhiversen
|
||||
homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/amcrest/* @pnbruckner
|
||||
homeassistant/components/androidtv/* @JeffLIrion
|
||||
homeassistant/components/apache_kafka/* @bachya
|
||||
homeassistant/components/api/* @home-assistant/core
|
||||
@ -59,7 +60,6 @@ homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren
|
||||
homeassistant/components/cisco_ios/* @fbradyirl
|
||||
homeassistant/components/cisco_mobility_express/* @fbradyirl
|
||||
homeassistant/components/cisco_webex_teams/* @fbradyirl
|
||||
homeassistant/components/ciscospark/* @fbradyirl
|
||||
homeassistant/components/cloud/* @home-assistant/cloud
|
||||
homeassistant/components/cloudflare/* @ludeeus
|
||||
homeassistant/components/comfoconnect/* @michaelarnauts
|
||||
@ -76,12 +76,14 @@ homeassistant/components/darksky/* @fabaff
|
||||
homeassistant/components/deconz/* @kane610
|
||||
homeassistant/components/delijn/* @bollewolle
|
||||
homeassistant/components/demo/* @home-assistant/core
|
||||
homeassistant/components/derivative/* @afaucogney
|
||||
homeassistant/components/device_automation/* @home-assistant/core
|
||||
homeassistant/components/digital_ocean/* @fabaff
|
||||
homeassistant/components/discogs/* @thibmaek
|
||||
homeassistant/components/doorbird/* @oblogic7
|
||||
homeassistant/components/dsmr_reader/* @depl0y
|
||||
homeassistant/components/dweet/* @fabaff
|
||||
homeassistant/components/dyson/* @etheralm
|
||||
homeassistant/components/ecobee/* @marthoc
|
||||
homeassistant/components/ecovacs/* @OverloadUT
|
||||
homeassistant/components/egardia/* @jeroenterheerdt
|
||||
@ -89,7 +91,6 @@ homeassistant/components/eight_sleep/* @mezz64
|
||||
homeassistant/components/elgato/* @frenck
|
||||
homeassistant/components/elv/* @majuss
|
||||
homeassistant/components/emby/* @mezz64
|
||||
homeassistant/components/emulated_hue/* @NobleKangaroo
|
||||
homeassistant/components/enigma2/* @fbradyirl
|
||||
homeassistant/components/enocean/* @bdurrer
|
||||
homeassistant/components/entur_public_transport/* @hfurubotten
|
||||
@ -115,6 +116,7 @@ homeassistant/components/foursquare/* @robbiet480
|
||||
homeassistant/components/freebox/* @snoof85
|
||||
homeassistant/components/fronius/* @nielstron
|
||||
homeassistant/components/frontend/* @home-assistant/frontend
|
||||
homeassistant/components/garmin_connect/* @cyberjunky
|
||||
homeassistant/components/gearbest/* @HerrHofrat
|
||||
homeassistant/components/geniushub/* @zxdavb
|
||||
homeassistant/components/geo_rss_events/* @exxamalte
|
||||
@ -129,6 +131,7 @@ homeassistant/components/google_cloud/* @lufton
|
||||
homeassistant/components/google_translate/* @awarecan
|
||||
homeassistant/components/google_travel_time/* @robbiet480
|
||||
homeassistant/components/gpsd/* @fabaff
|
||||
homeassistant/components/greeneye_monitor/* @jkeljo
|
||||
homeassistant/components/group/* @home-assistant/core
|
||||
homeassistant/components/growatt_server/* @indykoning
|
||||
homeassistant/components/gtfs/* @robbiet480
|
||||
@ -168,7 +171,7 @@ homeassistant/components/intent/* @home-assistant/core
|
||||
homeassistant/components/intesishome/* @jnimmo
|
||||
homeassistant/components/ios/* @robbiet480
|
||||
homeassistant/components/iperf3/* @rohankapoorcom
|
||||
homeassistant/components/ipma/* @dgomes
|
||||
homeassistant/components/ipma/* @dgomes @abmantis
|
||||
homeassistant/components/iqvia/* @bachya
|
||||
homeassistant/components/irish_rail_transport/* @ttroy50
|
||||
homeassistant/components/izone/* @Swamp-Ig
|
||||
@ -206,6 +209,7 @@ homeassistant/components/met/* @danielhiversen
|
||||
homeassistant/components/meteo_france/* @victorcerutti @oncleben31
|
||||
homeassistant/components/meteoalarm/* @rolfberkenbosch
|
||||
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
|
||||
homeassistant/components/mikrotik/* @engrbm87
|
||||
homeassistant/components/mill/* @danielhiversen
|
||||
homeassistant/components/min_max/* @fabaff
|
||||
homeassistant/components/minio/* @tkislan
|
||||
@ -219,9 +223,11 @@ homeassistant/components/msteams/* @peroyvind
|
||||
homeassistant/components/mysensors/* @MartinHjelmare
|
||||
homeassistant/components/mystrom/* @fabaff
|
||||
homeassistant/components/neato/* @dshokouhi @Santobert
|
||||
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
|
||||
homeassistant/components/nello/* @pschmitt
|
||||
homeassistant/components/ness_alarm/* @nickw444
|
||||
homeassistant/components/nest/* @awarecan
|
||||
homeassistant/components/netatmo/* @cgtobi
|
||||
homeassistant/components/netdata/* @fabaff
|
||||
homeassistant/components/nextbus/* @vividboarder
|
||||
homeassistant/components/nilu/* @hfurubotten
|
||||
@ -243,9 +249,9 @@ homeassistant/components/onewire/* @garbled1
|
||||
homeassistant/components/opentherm_gw/* @mvn23
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/openweathermap/* @fabaff
|
||||
homeassistant/components/opnsense/* @mtreinish
|
||||
homeassistant/components/orangepi_gpio/* @pascallj
|
||||
homeassistant/components/oru/* @bvlaicu
|
||||
homeassistant/components/owlet/* @oblogic7
|
||||
homeassistant/components/panel_custom/* @home-assistant/frontend
|
||||
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
||||
homeassistant/components/pcal9535a/* @Shulyaka
|
||||
@ -277,11 +283,13 @@ homeassistant/components/rfxtrx/* @danielhiversen
|
||||
homeassistant/components/ring/* @balloob
|
||||
homeassistant/components/rmvtransport/* @cgtobi
|
||||
homeassistant/components/roomba/* @pschmitt
|
||||
homeassistant/components/safe_mode/* @home-assistant/core
|
||||
homeassistant/components/saj/* @fredericvl
|
||||
homeassistant/components/samsungtv/* @escoand
|
||||
homeassistant/components/scene/* @home-assistant/core
|
||||
homeassistant/components/scrape/* @fabaff
|
||||
homeassistant/components/script/* @home-assistant/core
|
||||
homeassistant/components/search/* @home-assistant/core
|
||||
homeassistant/components/sense/* @kbickar
|
||||
homeassistant/components/sensibo/* @andrey-git
|
||||
homeassistant/components/sentry/* @dcramer
|
||||
@ -290,14 +298,17 @@ homeassistant/components/seventeentrack/* @bachya
|
||||
homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/shodan/* @fabaff
|
||||
homeassistant/components/sighthound/* @robmarkcole
|
||||
homeassistant/components/signal_messenger/* @bbernhard
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/sinch/* @bendikrb
|
||||
homeassistant/components/sisyphus/* @jkeljo
|
||||
homeassistant/components/slide/* @ualex73
|
||||
homeassistant/components/sma/* @kellerza
|
||||
homeassistant/components/smarthab/* @outadoc
|
||||
homeassistant/components/smartthings/* @andrewsayre
|
||||
homeassistant/components/smarty/* @z0mbieprocess
|
||||
homeassistant/components/sms/* @ocalvo
|
||||
homeassistant/components/smtp/* @fabaff
|
||||
homeassistant/components/solaredge_local/* @drobtravels @scheric
|
||||
homeassistant/components/solarlog/* @Ernst79
|
||||
@ -308,6 +319,7 @@ homeassistant/components/songpal/* @rytilahti
|
||||
homeassistant/components/spaceapi/* @fabaff
|
||||
homeassistant/components/speedtestdotnet/* @rohankapoorcom
|
||||
homeassistant/components/spider/* @peternijssen
|
||||
homeassistant/components/spotify/* @frenck
|
||||
homeassistant/components/sql/* @dgomes
|
||||
homeassistant/components/starline/* @anonym-tsk
|
||||
homeassistant/components/statistics/* @fabaff
|
||||
@ -351,6 +363,7 @@ homeassistant/components/tts/* @robbiet480
|
||||
homeassistant/components/twentemilieu/* @frenck
|
||||
homeassistant/components/twilio_call/* @robbiet480
|
||||
homeassistant/components/twilio_sms/* @robbiet480
|
||||
homeassistant/components/ubee/* @mzdrale
|
||||
homeassistant/components/unifi/* @kane610
|
||||
homeassistant/components/unifiled/* @florisvdk
|
||||
homeassistant/components/upc_connect/* @pvizeli
|
||||
@ -360,7 +373,7 @@ homeassistant/components/upnp/* @robbiet480
|
||||
homeassistant/components/uptimerobot/* @ludeeus
|
||||
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
||||
homeassistant/components/utility_meter/* @dgomes
|
||||
homeassistant/components/velbus/* @cereal2nd
|
||||
homeassistant/components/velbus/* @Cereal2nd @brefra
|
||||
homeassistant/components/velux/* @Julius2342
|
||||
homeassistant/components/versasense/* @flamm3blemuff1n
|
||||
homeassistant/components/version/* @fabaff
|
||||
|
@ -30,7 +30,7 @@ jobs:
|
||||
- template: templates/azp-job-wheels.yaml@azure
|
||||
parameters:
|
||||
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'
|
||||
wheelsRequirement: 'requirements_wheels.txt'
|
||||
wheelsRequirementDiff: 'requirements_diff.txt'
|
||||
@ -68,6 +68,7 @@ jobs:
|
||||
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
|
||||
sed -i "s|# py_noaa|py_noaa|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
|
||||
sed -i "s|# VL53L1X|VL53L1X|g" ${requirement_file}
|
||||
|
@ -6,13 +6,10 @@ import platform
|
||||
import subprocess
|
||||
import sys
|
||||
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__
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant import core
|
||||
|
||||
|
||||
def set_loop() -> None:
|
||||
"""Attempt to use different loop."""
|
||||
@ -78,19 +75,6 @@ def ensure_config_path(config_dir: str) -> None:
|
||||
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:
|
||||
"""Get parsed passed in arguments."""
|
||||
import homeassistant.config as config_util
|
||||
@ -107,7 +91,7 @@ def get_arguments() -> argparse.Namespace:
|
||||
help="Directory that contains the Home Assistant configuration",
|
||||
)
|
||||
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(
|
||||
"--debug", action="store_true", help="Start Home Assistant in debug mode"
|
||||
@ -253,34 +237,20 @@ def cmdline() -> List[str]:
|
||||
|
||||
async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
|
||||
"""Set up Home Assistant and run."""
|
||||
from homeassistant import bootstrap, core
|
||||
from homeassistant import bootstrap
|
||||
|
||||
hass = core.HomeAssistant()
|
||||
hass = await bootstrap.async_setup_hass(
|
||||
config_dir=config_dir,
|
||||
verbose=args.verbose,
|
||||
log_rotate_days=args.log_rotate_days,
|
||||
log_file=args.log_file,
|
||||
log_no_color=args.log_no_color,
|
||||
skip_pip=args.skip_pip,
|
||||
safe_mode=args.safe_mode,
|
||||
)
|
||||
|
||||
if args.demo_mode:
|
||||
config: Dict[str, Any] = {"frontend": {}, "demo": {}}
|
||||
bootstrap.async_from_config_dict(
|
||||
config,
|
||||
hass,
|
||||
config_dir=config_dir,
|
||||
verbose=args.verbose,
|
||||
skip_pip=args.skip_pip,
|
||||
log_rotate_days=args.log_rotate_days,
|
||||
log_file=args.log_file,
|
||||
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,
|
||||
log_rotate_days=args.log_rotate_days,
|
||||
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:
|
||||
import webbrowser
|
||||
@ -358,7 +328,7 @@ def main() -> int:
|
||||
|
||||
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)
|
||||
|
||||
# Daemon functions
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Provide methods to bootstrap a Home Assistant instance."""
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
@ -11,6 +10,7 @@ from typing import Any, Dict, Optional, Set
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config as conf_util, config_entries, core, loader
|
||||
from homeassistant.components import http
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_CLOSE,
|
||||
REQUIRED_NEXT_PYTHON_DATE,
|
||||
@ -42,16 +42,68 @@ STAGE_1_INTEGRATIONS = {
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_hass(
|
||||
*,
|
||||
config_dir: str,
|
||||
verbose: bool,
|
||||
log_rotate_days: int,
|
||||
log_file: str,
|
||||
log_no_color: bool,
|
||||
skip_pip: bool,
|
||||
safe_mode: bool,
|
||||
) -> Optional[core.HomeAssistant]:
|
||||
"""Set up Home Assistant."""
|
||||
hass = core.HomeAssistant()
|
||||
hass.config.config_dir = config_dir
|
||||
|
||||
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
|
||||
|
||||
hass.config.skip_pip = skip_pip
|
||||
if skip_pip:
|
||||
_LOGGER.warning(
|
||||
"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,
|
||||
config_dir: Optional[str] = None,
|
||||
enable_log: bool = True,
|
||||
verbose: bool = False,
|
||||
skip_pip: bool = False,
|
||||
log_rotate_days: Any = None,
|
||||
log_file: Any = None,
|
||||
log_no_color: bool = False,
|
||||
config: Dict[str, Any], hass: core.HomeAssistant
|
||||
) -> Optional[core.HomeAssistant]:
|
||||
"""Try to configure Home Assistant from a configuration dictionary.
|
||||
|
||||
@ -60,15 +112,6 @@ async def async_from_config_dict(
|
||||
"""
|
||||
start = time()
|
||||
|
||||
if enable_log:
|
||||
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
|
||||
|
||||
hass.config.skip_pip = skip_pip
|
||||
if skip_pip:
|
||||
_LOGGER.warning(
|
||||
"Skipping pip installation of required modules. This may cause issues"
|
||||
)
|
||||
|
||||
core_config = config.get(core.DOMAIN, {})
|
||||
|
||||
try:
|
||||
@ -83,14 +126,6 @@ async def async_from_config_dict(
|
||||
)
|
||||
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)
|
||||
await hass.config_entries.async_initialize()
|
||||
|
||||
@ -116,46 +151,6 @@ async def async_from_config_dict(
|
||||
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
|
||||
def async_enable_logging(
|
||||
hass: core.HomeAssistant,
|
||||
@ -269,7 +264,8 @@ 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)
|
||||
|
||||
# Add config entry domains
|
||||
domains.update(hass.config_entries.async_domains())
|
||||
if "safe_mode" not in config:
|
||||
domains.update(hass.config_entries.async_domains())
|
||||
|
||||
# Make sure the Hass.io component is loaded
|
||||
if "HASSIO" in os.environ:
|
||||
|
@ -21,11 +21,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
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):
|
||||
"""Set up Abode alarm control panel device."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -13,11 +13,6 @@ from .const import DOMAIN, SIGNAL_TRIGGER_QUICK_ACTION
|
||||
_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):
|
||||
"""Set up Abode binary sensor devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -18,11 +18,6 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
_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):
|
||||
"""Set up Abode camera devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -11,11 +11,6 @@ from .const import DOMAIN
|
||||
_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):
|
||||
"""Set up Abode cover devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -24,11 +24,6 @@ from .const import DOMAIN
|
||||
_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):
|
||||
"""Set up Abode light devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -11,11 +11,6 @@ from .const import DOMAIN
|
||||
_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):
|
||||
"""Set up Abode lock devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Abode",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||
"requirements": ["abodepy==0.16.7"],
|
||||
"requirements": ["abodepy==0.17.0"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@shred86"]
|
||||
}
|
||||
|
@ -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):
|
||||
"""Set up Abode sensor devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -12,11 +12,6 @@ from .const import DOMAIN
|
||||
_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):
|
||||
"""Set up Abode switch devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
10
homeassistant/components/adguard/.translations/cs.json
Normal file
10
homeassistant/components/adguard/.translations/cs.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"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_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}.",
|
||||
"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, du hast {current_version}.",
|
||||
"existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.",
|
||||
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig."
|
||||
},
|
||||
|
@ -142,11 +142,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool
|
||||
class AdGuardHomeEntity(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."""
|
||||
self._name = name
|
||||
self._icon = icon
|
||||
self._available = True
|
||||
self._enabled_default = enabled_default
|
||||
self._icon = icon
|
||||
self._name = name
|
||||
self.adguard = adguard
|
||||
|
||||
@property
|
||||
@ -159,6 +162,11 @@ class AdGuardHomeEntity(Entity):
|
||||
"""Return the mdi icon of the entity."""
|
||||
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
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
@ -166,6 +174,9 @@ class AdGuardHomeEntity(Entity):
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._adguard_update()
|
||||
self._available = True
|
||||
|
@ -51,14 +51,20 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity):
|
||||
"""Defines a AdGuard Home sensor."""
|
||||
|
||||
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:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
self._state = None
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self.measurement = measurement
|
||||
|
||||
super().__init__(adguard, name, icon)
|
||||
super().__init__(adguard, name, icon, enabled_default)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
@ -109,6 +115,7 @@ class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor):
|
||||
"mdi:magnify-close",
|
||||
"blocked_filtering",
|
||||
"queries",
|
||||
enabled_default=False,
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
@ -214,7 +221,12 @@ class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
|
||||
def __init__(self, adguard):
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
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:
|
||||
|
@ -10,9 +10,9 @@ from homeassistant.components.adguard.const import (
|
||||
DATA_ADGUARD_VERION,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -45,14 +45,16 @@ async def async_setup_entry(
|
||||
async_add_entities(switches, True)
|
||||
|
||||
|
||||
class AdGuardHomeSwitch(ToggleEntity, AdGuardHomeDeviceEntity):
|
||||
class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchDevice):
|
||||
"""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."""
|
||||
self._state = False
|
||||
self._key = key
|
||||
super().__init__(adguard, name, icon)
|
||||
super().__init__(adguard, name, icon, enabled_default)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
@ -204,7 +206,13 @@ class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch):
|
||||
|
||||
def __init__(self, adguard) -> None:
|
||||
"""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:
|
||||
"""Turn off the switch."""
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Airly-integration for disse koordinater er allerede konfigureret."
|
||||
},
|
||||
"error": {
|
||||
"auth": "API-n\u00f8glen er ikke korrekt.",
|
||||
"name_exists": "Navnet findes allerede.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Die Airly-Integration ist f\u00fcr diese Koordinaten bereits konfiguriert."
|
||||
},
|
||||
"error": {
|
||||
"auth": "Der API-Schl\u00fcssel ist nicht korrekt.",
|
||||
"name_exists": "Name existiert bereits",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Airly integration for these coordinates is already configured."
|
||||
},
|
||||
"error": {
|
||||
"auth": "API key is not correct.",
|
||||
"name_exists": "Name already exists.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "La integraci\u00f3n a\u00e9rea para estas coordenadas ya est\u00e1 configurada."
|
||||
},
|
||||
"error": {
|
||||
"auth": "La clave de la API no es correcta.",
|
||||
"name_exists": "El nombre ya existe.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "L'int\u00e9gration des coordonn\u00e9es d'Airly est d\u00e9j\u00e0 configur\u00e9."
|
||||
},
|
||||
"error": {
|
||||
"auth": "La cl\u00e9 API n'est pas correcte.",
|
||||
"name_exists": "Le nom existe d\u00e9j\u00e0.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "L'integrazione Airly per queste coordinate \u00e8 gi\u00e0 configurata."
|
||||
},
|
||||
"error": {
|
||||
"auth": "La chiave API non \u00e8 corretta.",
|
||||
"name_exists": "Il nome \u00e8 gi\u00e0 esistente",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"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": {
|
||||
"auth": "API \ud0a4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.",
|
||||
"name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Airly Integratioun fir d\u00ebs Koordinaten ass scho konfigur\u00e9iert."
|
||||
},
|
||||
"error": {
|
||||
"auth": "Api Schl\u00ebssel ass net korrekt.",
|
||||
"name_exists": "Numm g\u00ebtt et schonn",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Airly integrering for disse koordinatene er allerede konfigurert."
|
||||
},
|
||||
"error": {
|
||||
"auth": "API-n\u00f8kkelen er ikke korrekt.",
|
||||
"name_exists": "Navnet finnes allerede.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Integracja Airly dla tych wsp\u00f3\u0142rz\u0119dnych jest ju\u017c skonfigurowana."
|
||||
},
|
||||
"error": {
|
||||
"auth": "Klucz API jest nieprawid\u0142owy.",
|
||||
"name_exists": "Nazwa ju\u017c istnieje.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"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": {
|
||||
"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.",
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u6b64 Airly \u6574\u5408\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002"
|
||||
},
|
||||
"error": {
|
||||
"auth": "API \u5bc6\u9470\u4e0d\u6b63\u78ba\u3002",
|
||||
"name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728",
|
||||
|
@ -41,6 +41,12 @@ async def async_setup_entry(hass, config_entry):
|
||||
latitude = config_entry.data[CONF_LATITUDE]
|
||||
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)
|
||||
|
||||
airly = AirlyData(websession, api_key, latitude, longitude)
|
||||
|
@ -5,7 +5,7 @@ from homeassistant.components.air_quality import (
|
||||
ATTR_PM_10,
|
||||
AirQualityEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.const import CONF_NAME
|
||||
|
||||
from .const import (
|
||||
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):
|
||||
"""Set up Airly air_quality entity based on a config entry."""
|
||||
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]
|
||||
|
||||
async_add_entities([AirlyAirQuality(data, name, unique_id)], True)
|
||||
async_add_entities([AirlyAirQuality(data, name, config_entry.unique_id)], True)
|
||||
|
||||
|
||||
def round_state(func):
|
||||
|
@ -6,19 +6,14 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
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
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
|
||||
|
||||
|
||||
@callback
|
||||
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)
|
||||
)
|
||||
from .const import ( # pylint:disable=unused-import
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
NO_AIRLY_SENSORS,
|
||||
)
|
||||
|
||||
|
||||
class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
@ -38,8 +33,10 @@ class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
websession = async_get_clientsession(self.hass)
|
||||
|
||||
if user_input is not None:
|
||||
if user_input[CONF_NAME] in configured_instances(self.hass):
|
||||
self._errors[CONF_NAME] = "name_exists"
|
||||
await self.async_set_unique_id(
|
||||
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"])
|
||||
if not api_key_valid:
|
||||
self._errors["base"] = "auth"
|
||||
|
@ -2,8 +2,6 @@
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DEVICE_CLASS,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_NAME,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
@ -62,14 +60,12 @@ SENSOR_TYPES = {
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Airly sensor entities based on a config entry."""
|
||||
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]
|
||||
|
||||
sensors = []
|
||||
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))
|
||||
|
||||
async_add_entities(sensors, True)
|
||||
|
@ -14,9 +14,11 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"name_exists": "Name already exists.",
|
||||
"wrong_location": "No Airly measuring stations in this area.",
|
||||
"auth": "API key is not correct."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Airly integration for these coordinates is already configured."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
{
|
||||
"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": {
|
||||
"armed_away": "{entity_name} Unterwegs",
|
||||
"armed_home": "{entity_name} Zuhause",
|
||||
|
@ -121,67 +121,49 @@ class AlarmControlPanel(Entity):
|
||||
"""Send disarm command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_executor_job(self.alarm_disarm, code)
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
await self.hass.async_add_executor_job(self.alarm_disarm, code)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command.
|
||||
|
||||
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)
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
await self.hass.async_add_executor_job(self.alarm_arm_home, code)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command.
|
||||
|
||||
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)
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
await self.hass.async_add_executor_job(self.alarm_arm_away, code)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command.
|
||||
|
||||
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)
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
await self.hass.async_add_executor_job(self.alarm_arm_night, code)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_executor_job(self.alarm_trigger, code)
|
||||
async def async_alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command."""
|
||||
await self.hass.async_add_executor_job(self.alarm_trigger, code)
|
||||
|
||||
def alarm_arm_custom_bypass(self, code=None):
|
||||
"""Send arm custom bypass command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_arm_custom_bypass(self, code=None):
|
||||
"""Send arm custom bypass command.
|
||||
|
||||
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)
|
||||
async def async_alarm_arm_custom_bypass(self, code=None):
|
||||
"""Send arm custom bypass command."""
|
||||
await self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
|
@ -118,11 +118,12 @@ def setup(hass, config):
|
||||
conf = config.get(DOMAIN)
|
||||
|
||||
restart = False
|
||||
device = conf.get(CONF_DEVICE)
|
||||
display = conf.get(CONF_PANEL_DISPLAY)
|
||||
device = conf[CONF_DEVICE]
|
||||
display = conf[CONF_PANEL_DISPLAY]
|
||||
auto_bypass = conf[CONF_AUTO_BYPASS]
|
||||
zones = conf.get(CONF_ZONES)
|
||||
|
||||
device_type = device.get(CONF_DEVICE_TYPE)
|
||||
device_type = device[CONF_DEVICE_TYPE]
|
||||
host = DEFAULT_DEVICE_HOST
|
||||
port = DEFAULT_DEVICE_PORT
|
||||
path = DEFAULT_DEVICE_PATH
|
||||
@ -204,7 +205,9 @@ def setup(hass, config):
|
||||
|
||||
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:
|
||||
load_platform(hass, "binary_sensor", DOMAIN, {CONF_ZONES: zones}, config)
|
||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
||||
)
|
||||
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__)
|
||||
|
||||
@ -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):
|
||||
"""Set up for AlarmDecoder alarm panels."""
|
||||
device = AlarmDecoderAlarmPanel(discovery_info["autobypass"])
|
||||
add_entities([device])
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
auto_bypass = discovery_info[CONF_AUTO_BYPASS]
|
||||
entity = AlarmDecoderAlarmPanel(auto_bypass)
|
||||
add_entities([entity])
|
||||
|
||||
def alarm_toggle_chime_handler(service):
|
||||
"""Register toggle chime handler."""
|
||||
code = service.data.get(ATTR_CODE)
|
||||
device.alarm_toggle_chime(code)
|
||||
entity.alarm_toggle_chime(code)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN,
|
||||
@ -53,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
def alarm_keypress_handler(service):
|
||||
"""Register keypress handler."""
|
||||
keypress = service.data[ATTR_KEYPRESS]
|
||||
device.alarm_keypress(keypress)
|
||||
entity.alarm_keypress(keypress)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN,
|
||||
|
@ -2,7 +2,9 @@
|
||||
"domain": "alarmdecoder",
|
||||
"name": "AlarmDecoder",
|
||||
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
|
||||
"requirements": ["alarmdecoder==1.13.9"],
|
||||
"requirements": [
|
||||
"alarmdecoder==1.13.2"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
await self._alarm.async_alarm_disarm()
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm hom command."""
|
||||
"""Send arm home command."""
|
||||
if self._validate_code(code):
|
||||
await self._alarm.async_alarm_arm_home()
|
||||
|
||||
|
@ -1,8 +1,20 @@
|
||||
"""Alexa capabilities."""
|
||||
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.const import (
|
||||
SUPPORT_ALARM_ARM_AWAY,
|
||||
SUPPORT_ALARM_ARM_HOME,
|
||||
SUPPORT_ALARM_ARM_NIGHT,
|
||||
)
|
||||
import homeassistant.components.climate.const as climate
|
||||
import homeassistant.components.media_player.const as media_player
|
||||
from homeassistant.const import (
|
||||
@ -31,7 +43,6 @@ from .const import (
|
||||
API_THERMOSTAT_PRESETS,
|
||||
DATE_FORMAT,
|
||||
PERCENTAGE_FAN_MAP,
|
||||
RANGE_FAN_MAP,
|
||||
Inputs,
|
||||
)
|
||||
from .errors import UnsupportedProperty
|
||||
@ -503,6 +514,10 @@ class AlexaColorController(AlexaCapability):
|
||||
"""Return what properties this entity supports."""
|
||||
return [{"name": "color"}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
"""Return True if properties asynchronously reported."""
|
||||
return True
|
||||
|
||||
def properties_retrievable(self):
|
||||
"""Return True if properties can be retrieved."""
|
||||
return True
|
||||
@ -548,6 +563,10 @@ class AlexaColorTemperatureController(AlexaCapability):
|
||||
"""Return what properties this entity supports."""
|
||||
return [{"name": "colorTemperatureInKelvin"}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
"""Return True if properties asynchronously reported."""
|
||||
return True
|
||||
|
||||
def properties_retrievable(self):
|
||||
"""Return True if properties can be retrieved."""
|
||||
return True
|
||||
@ -590,6 +609,10 @@ class AlexaPercentageController(AlexaCapability):
|
||||
"""Return what properties this entity supports."""
|
||||
return [{"name": "percentage"}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
"""Return True if properties asynchronously reported."""
|
||||
return True
|
||||
|
||||
def properties_retrievable(self):
|
||||
"""Return True if properties can be retrieved."""
|
||||
return True
|
||||
@ -1064,10 +1087,23 @@ class AlexaSecurityPanelController(AlexaCapability):
|
||||
def configuration(self):
|
||||
"""Return configuration object with supported authorization types."""
|
||||
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:
|
||||
return {"supportedAuthorizationTypes": [{"type": "FOUR_DIGIT_PIN"}]}
|
||||
return None
|
||||
configuration["supportedAuthorizationTypes"] = [{"type": "FOUR_DIGIT_PIN"}]
|
||||
|
||||
return configuration
|
||||
|
||||
|
||||
class AlexaModeController(AlexaCapability):
|
||||
@ -1185,7 +1221,10 @@ class AlexaModeController(AlexaCapability):
|
||||
f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}",
|
||||
[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 None
|
||||
@ -1287,10 +1326,20 @@ class AlexaRangeController(AlexaCapability):
|
||||
if name != "rangeValue":
|
||||
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
|
||||
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)
|
||||
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
|
||||
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}":
|
||||
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
|
||||
|
||||
def configuration(self):
|
||||
@ -1318,24 +1377,26 @@ class AlexaRangeController(AlexaCapability):
|
||||
|
||||
# Fan Speed Resources
|
||||
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(
|
||||
labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED],
|
||||
min_value=1,
|
||||
max_value=3,
|
||||
min_value=0,
|
||||
max_value=max_value,
|
||||
precision=1,
|
||||
)
|
||||
self._resource.add_preset(
|
||||
value=1,
|
||||
labels=[AlexaGlobalCatalog.VALUE_LOW, AlexaGlobalCatalog.VALUE_MINIMUM],
|
||||
)
|
||||
self._resource.add_preset(value=2, labels=[AlexaGlobalCatalog.VALUE_MEDIUM])
|
||||
self._resource.add_preset(
|
||||
value=3,
|
||||
labels=[
|
||||
AlexaGlobalCatalog.VALUE_HIGH,
|
||||
AlexaGlobalCatalog.VALUE_MAXIMUM,
|
||||
],
|
||||
)
|
||||
for index, speed in enumerate(speed_list):
|
||||
labels = []
|
||||
if isinstance(speed, str):
|
||||
labels.append(speed.replace("_", " "))
|
||||
if index == 1:
|
||||
labels.append(AlexaGlobalCatalog.VALUE_MINIMUM)
|
||||
if index == max_value:
|
||||
labels.append(AlexaGlobalCatalog.VALUE_MAXIMUM)
|
||||
|
||||
if len(labels) > 0:
|
||||
self._resource.add_preset(value=index, labels=labels)
|
||||
|
||||
return self._resource.serialize_capability_resources()
|
||||
|
||||
# Cover Position Resources
|
||||
@ -1368,7 +1429,7 @@ class AlexaRangeController(AlexaCapability):
|
||||
unit = self.entity.attributes.get(input_number.ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
self._resource = AlexaPresetResource(
|
||||
["Value"],
|
||||
["Value", AlexaGlobalCatalog.SETTING_PRESET],
|
||||
min_value=min_value,
|
||||
max_value=max_value,
|
||||
precision=precision,
|
||||
@ -1382,6 +1443,26 @@ class AlexaRangeController(AlexaCapability):
|
||||
)
|
||||
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
|
||||
|
||||
def semantics(self):
|
||||
@ -1701,3 +1782,29 @@ class AlexaEqualizerController(AlexaCapability):
|
||||
configurations = {"modes": {"supported": supported_sound_modes}}
|
||||
|
||||
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}
|
||||
|
@ -84,20 +84,6 @@ PERCENTAGE_FAN_MAP = {
|
||||
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:
|
||||
"""Possible causes for property changes.
|
||||
|
@ -19,6 +19,8 @@ from homeassistant.components import (
|
||||
script,
|
||||
sensor,
|
||||
switch,
|
||||
timer,
|
||||
vacuum,
|
||||
)
|
||||
from homeassistant.components.climate import const as climate
|
||||
from homeassistant.const import (
|
||||
@ -61,6 +63,7 @@ from .capabilities import (
|
||||
AlexaStepSpeaker,
|
||||
AlexaTemperatureSensor,
|
||||
AlexaThermostatController,
|
||||
AlexaTimeHoldController,
|
||||
AlexaToggleController,
|
||||
)
|
||||
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
|
||||
@ -255,6 +258,9 @@ class AlexaEntity:
|
||||
def serialize_properties(self):
|
||||
"""Yield each supported property in API format."""
|
||||
for interface in self.interfaces():
|
||||
if not interface.properties_proactively_reported():
|
||||
continue
|
||||
|
||||
for prop in interface.serialize_properties():
|
||||
yield prop
|
||||
|
||||
@ -394,6 +400,7 @@ class CoverCapabilities(AlexaEntity):
|
||||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
yield AlexaPowerController(self.entity)
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & cover.SUPPORT_SET_POSITION:
|
||||
yield AlexaRangeController(
|
||||
@ -703,3 +710,48 @@ class InputNumberCapabilities(AlexaEntity):
|
||||
)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
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)
|
||||
|
@ -10,6 +10,8 @@ from homeassistant.components import (
|
||||
input_number,
|
||||
light,
|
||||
media_player,
|
||||
timer,
|
||||
vacuum,
|
||||
)
|
||||
from homeassistant.components.climate import const as climate
|
||||
from homeassistant.const import (
|
||||
@ -50,8 +52,6 @@ from .const import (
|
||||
API_THERMOSTAT_MODES_CUSTOM,
|
||||
API_THERMOSTAT_PRESETS,
|
||||
PERCENTAGE_FAN_MAP,
|
||||
RANGE_FAN_MAP,
|
||||
SPEED_FAN_MAP,
|
||||
Cause,
|
||||
Inputs,
|
||||
)
|
||||
@ -119,7 +119,9 @@ async def async_api_turn_on(hass, config, directive, context):
|
||||
domain = ha.DOMAIN
|
||||
|
||||
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)
|
||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||
if not supported & power_features:
|
||||
@ -145,7 +147,9 @@ async def async_api_turn_off(hass, config, directive, context):
|
||||
domain = ha.DOMAIN
|
||||
|
||||
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)
|
||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||
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
|
||||
)
|
||||
|
||||
# return 0 until alarm integration supports an exit delay
|
||||
payload = {"exitDelayInSeconds": 0}
|
||||
|
||||
response = directive.response(
|
||||
name="Arm.Response", namespace="Alexa.SecurityPanelController"
|
||||
name="Arm.Response", namespace="Alexa.SecurityPanelController", payload=payload
|
||||
)
|
||||
|
||||
response.add_context_property(
|
||||
@ -928,6 +935,12 @@ async def async_api_disarm(hass, config, directive, context):
|
||||
"""Process a Security Panel Disarm request."""
|
||||
entity = directive.entity
|
||||
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
|
||||
if "authorization" in payload:
|
||||
@ -941,7 +954,6 @@ async def async_api_disarm(hass, config, directive, context):
|
||||
msg = "Invalid Code"
|
||||
raise AlexaSecurityPanelUnauthorizedError(msg)
|
||||
|
||||
response = directive.response()
|
||||
response.add_context_property(
|
||||
{
|
||||
"name": "armState",
|
||||
@ -1095,8 +1107,10 @@ async def async_api_set_range(hass, config, directive, context):
|
||||
|
||||
# Fan Speed
|
||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
|
||||
range_value = int(range_value)
|
||||
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:
|
||||
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
|
||||
else:
|
||||
service = cover.SERVICE_SET_COVER_TILT_POSITION
|
||||
data[cover.ATTR_POSITION] = range_value
|
||||
data[cover.ATTR_TILT_POSITION] = range_value
|
||||
|
||||
# Input Number 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])
|
||||
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:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
@ -1167,15 +1195,23 @@ async def async_api_adjust_range(hass, config, directive, context):
|
||||
service = None
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
range_delta = directive.payload["rangeValueDelta"]
|
||||
range_delta_default = bool(directive.payload["rangeValueDeltaDefault"])
|
||||
response_value = 0
|
||||
|
||||
# Fan Speed
|
||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
|
||||
range_delta = int(range_delta)
|
||||
service = fan.SERVICE_SET_SPEED
|
||||
current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0)
|
||||
speed = SPEED_FAN_MAP.get(
|
||||
min(3, max(0, range_delta + current_range)), fan.SPEED_OFF
|
||||
speed_list = entity.attributes[fan.ATTR_SPEED_LIST]
|
||||
current_speed = entity.attributes[fan.ATTR_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
|
||||
)
|
||||
|
||||
if speed == fan.SPEED_OFF:
|
||||
@ -1185,21 +1221,37 @@ async def async_api_adjust_range(hass, config, directive, context):
|
||||
|
||||
# Cover 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
|
||||
current = entity.attributes.get(cover.ATTR_POSITION)
|
||||
data[cover.ATTR_POSITION] = response_value = min(
|
||||
100, max(0, range_delta + current)
|
||||
)
|
||||
if not 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
|
||||
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
|
||||
current = entity.attributes.get(cover.ATTR_TILT_POSITION)
|
||||
data[cover.ATTR_TILT_POSITION] = response_value = min(
|
||||
100, max(0, range_delta + current)
|
||||
)
|
||||
if not 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
|
||||
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)
|
||||
)
|
||||
|
||||
# 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:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
@ -1396,3 +1466,49 @@ async def async_api_bands_directive(hass, config, directive, context):
|
||||
# Currently bands directives are not supported.
|
||||
msg = "Entity does not support directive"
|
||||
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()
|
||||
|
@ -6,6 +6,9 @@
|
||||
"missing_configuration": "Consulta la documentaci\u00f3 sobre com configurar Almond."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"title": "Almond (complement de Hass.io)"
|
||||
},
|
||||
"pick_implementation": {
|
||||
"title": "Selecci\u00f3 del m\u00e8tode d'autenticaci\u00f3"
|
||||
}
|
||||
|
10
homeassistant/components/almond/.translations/cs.json
Normal file
10
homeassistant/components/almond/.translations/cs.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Tjek venligst dokumentationen om, hvordan man indstiller Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "V\u00e6lg godkendelsesmetode"
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"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.",
|
||||
"missing_configuration": "Bitte \u00fcberpr\u00fcfen Sie die Dokumentation zur Einrichtung von Almond."
|
||||
"missing_configuration": "Bitte \u00fcberpr\u00fcfe die Dokumentation zur Einrichtung von Almond."
|
||||
},
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Please check the documentation on how to set up Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "Pick Authentication Method"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "Seleccione el m\u00e9todo de autenticaci\u00f3n"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "S\u00e9lectionner une m\u00e9thode d'authentification"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Si prega di controllare la documentazione su come impostare Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "Seleziona metodo di autenticazione"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694."
|
||||
},
|
||||
"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": {
|
||||
"title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "Wielt Authentifikatiouns Method aus"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "Velg autentiseringsmetode"
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "Prosz\u0119 zapozna\u0107 si\u0119 z dokumentacj\u0105 konfiguracji Almond."
|
||||
},
|
||||
"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": {
|
||||
"title": "Wybierz metod\u0119 uwierzytelniania"
|
||||
}
|
||||
|
@ -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."
|
||||
},
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
|
9
homeassistant/components/almond/.translations/sv.json
Normal file
9
homeassistant/components/almond/.translations/sv.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"title": "Almond via Hass.io-till\u00e4gget"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,10 @@
|
||||
"missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002"
|
||||
},
|
||||
"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": {
|
||||
"title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f"
|
||||
}
|
||||
|
@ -3,6 +3,10 @@
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"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": {
|
||||
|
@ -30,11 +30,6 @@ from .const import (
|
||||
_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):
|
||||
"""Set up Ambient PWS binary sensors based on a config entry."""
|
||||
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
@ -20,11 +20,6 @@ from .const import (
|
||||
_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):
|
||||
"""Set up Ambient PWS sensors based on a config entry."""
|
||||
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
@ -256,7 +256,7 @@ def setup(hass, config):
|
||||
async_dispatcher_send(hass, service_signal(call.service, entity_id), *args)
|
||||
|
||||
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
|
||||
|
||||
|
@ -4,5 +4,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/amcrest",
|
||||
"requirements": ["amcrest==1.5.3"],
|
||||
"dependencies": ["ffmpeg"],
|
||||
"codeowners": []
|
||||
"codeowners": ["@pnbruckner"]
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||
"requirements": [
|
||||
"adb-shell==0.1.1",
|
||||
"androidtv==0.0.38",
|
||||
"androidtv==0.0.39",
|
||||
"pure-python-adb==0.2.2.dev0"
|
||||
],
|
||||
"dependencies": [],
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.components.media_player.const import (
|
||||
SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON,
|
||||
SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
@ -59,6 +60,7 @@ SUPPORT_ANDROIDTV = (
|
||||
| SUPPORT_SELECT_SOURCE
|
||||
| SUPPORT_STOP
|
||||
| SUPPORT_VOLUME_MUTE
|
||||
| SUPPORT_VOLUME_SET
|
||||
| SUPPORT_VOLUME_STEP
|
||||
)
|
||||
|
||||
@ -80,6 +82,7 @@ CONF_ADBKEY = "adbkey"
|
||||
CONF_ADB_SERVER_IP = "adb_server_ip"
|
||||
CONF_ADB_SERVER_PORT = "adb_server_port"
|
||||
CONF_APPS = "apps"
|
||||
CONF_EXCLUDE_UNNAMED_APPS = "exclude_unnamed_apps"
|
||||
CONF_GET_SOURCES = "get_sources"
|
||||
CONF_STATE_DETECTION_RULES = "state_detection_rules"
|
||||
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_PORT, default=DEFAULT_ADB_SERVER_PORT): cv.port,
|
||||
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_OFF_COMMAND): cv.string,
|
||||
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
|
||||
{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.get(CONF_TURN_ON_COMMAND),
|
||||
config.get(CONF_TURN_OFF_COMMAND),
|
||||
config[CONF_EXCLUDE_UNNAMED_APPS],
|
||||
]
|
||||
|
||||
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
|
||||
@ -365,7 +372,14 @@ class ADBDevice(MediaPlayerDevice):
|
||||
"""Representation of an Android TV or Fire TV device."""
|
||||
|
||||
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."""
|
||||
self.aftv = aftv
|
||||
@ -373,7 +387,7 @@ class ADBDevice(MediaPlayerDevice):
|
||||
self._app_id_to_name = APPS.copy()
|
||||
self._app_id_to_name.update(apps)
|
||||
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._keys = KEYS
|
||||
@ -384,12 +398,15 @@ class ADBDevice(MediaPlayerDevice):
|
||||
self.turn_on_command = turn_on_command
|
||||
self.turn_off_command = turn_off_command
|
||||
|
||||
self._exclude_unnamed_apps = exclude_unnamed_apps
|
||||
|
||||
# ADB exceptions to catch
|
||||
if not self.aftv.adb_server_ip:
|
||||
# Using "adb_shell" (Python ADB implementation)
|
||||
self.exceptions = (
|
||||
AttributeError,
|
||||
BrokenPipeError,
|
||||
ConnectionResetError,
|
||||
TypeError,
|
||||
ValueError,
|
||||
InvalidChecksumError,
|
||||
@ -558,11 +575,24 @@ class AndroidTVDevice(ADBDevice):
|
||||
"""Representation of an Android TV device."""
|
||||
|
||||
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."""
|
||||
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
|
||||
@ -600,9 +630,13 @@ class AndroidTVDevice(ADBDevice):
|
||||
self._available = False
|
||||
|
||||
if running_apps:
|
||||
self._sources = [
|
||||
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
|
||||
sources = [
|
||||
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:
|
||||
self._sources = None
|
||||
|
||||
@ -631,6 +665,11 @@ class AndroidTVDevice(ADBDevice):
|
||||
"""Mute the 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()
|
||||
def volume_down(self):
|
||||
"""Send volume down command."""
|
||||
@ -670,9 +709,13 @@ class FireTVDevice(ADBDevice):
|
||||
self._available = False
|
||||
|
||||
if running_apps:
|
||||
self._sources = [
|
||||
self._app_id_to_name.get(app_id, app_id) for app_id in running_apps
|
||||
sources = [
|
||||
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:
|
||||
self._sources = None
|
||||
|
||||
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_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)
|
||||
|
||||
@callback
|
||||
def async_anthemav_update_callback(message):
|
||||
"""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())
|
||||
|
||||
avr = await anthemav.Connection.create(
|
||||
|
@ -411,6 +411,7 @@ async def async_services_json(hass):
|
||||
return [{"domain": key, "services": value} for key, value in descriptions.items()]
|
||||
|
||||
|
||||
@ha.callback
|
||||
def async_events_json(hass):
|
||||
"""Generate event data to JSONify."""
|
||||
return [
|
||||
|
@ -229,62 +229,42 @@ class AppleTvDevice(MediaPlayerDevice):
|
||||
self._playing = None
|
||||
self._power.set_power_on(False)
|
||||
|
||||
def async_media_play_pause(self):
|
||||
"""Pause media on media player.
|
||||
async def async_media_play_pause(self):
|
||||
"""Pause media on media player."""
|
||||
if not self._playing:
|
||||
return
|
||||
state = self.state
|
||||
if state == STATE_PAUSED:
|
||||
await self.atv.remote_control.play()
|
||||
elif state == STATE_PLAYING:
|
||||
await self.atv.remote_control.pause()
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async def async_media_play(self):
|
||||
"""Play media."""
|
||||
if self._playing:
|
||||
state = self.state
|
||||
if state == STATE_PAUSED:
|
||||
return self.atv.remote_control.play()
|
||||
if state == STATE_PLAYING:
|
||||
return self.atv.remote_control.pause()
|
||||
await self.atv.remote_control.play()
|
||||
|
||||
def async_media_play(self):
|
||||
"""Play media.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async def async_media_stop(self):
|
||||
"""Stop the media player."""
|
||||
if self._playing:
|
||||
return self.atv.remote_control.play()
|
||||
await self.atv.remote_control.stop()
|
||||
|
||||
def async_media_stop(self):
|
||||
"""Stop the media player.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async def async_media_pause(self):
|
||||
"""Pause the media player."""
|
||||
if self._playing:
|
||||
return self.atv.remote_control.stop()
|
||||
await self.atv.remote_control.pause()
|
||||
|
||||
def async_media_pause(self):
|
||||
"""Pause the media player.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async def async_media_next_track(self):
|
||||
"""Send next track command."""
|
||||
if self._playing:
|
||||
return self.atv.remote_control.pause()
|
||||
await self.atv.remote_control.next()
|
||||
|
||||
def async_media_next_track(self):
|
||||
"""Send next track command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async def async_media_previous_track(self):
|
||||
"""Send previous track command."""
|
||||
if self._playing:
|
||||
return self.atv.remote_control.next()
|
||||
await self.atv.remote_control.previous()
|
||||
|
||||
def async_media_previous_track(self):
|
||||
"""Send previous track command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async def async_media_seek(self, position):
|
||||
"""Send seek command."""
|
||||
if self._playing:
|
||||
return self.atv.remote_control.previous()
|
||||
|
||||
def async_media_seek(self, position):
|
||||
"""Send seek command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
if self._playing:
|
||||
return self.atv.remote_control.set_position(position)
|
||||
await self.atv.remote_control.set_position(position)
|
||||
|
@ -61,17 +61,10 @@ class AppleTVRemote(remote.RemoteDevice):
|
||||
"""
|
||||
self._power.set_power_on(False)
|
||||
|
||||
def async_send_command(self, command, **kwargs):
|
||||
"""Send a command to one device.
|
||||
async def async_send_command(self, command, **kwargs):
|
||||
"""Send a command to one device."""
|
||||
for single_command in command:
|
||||
if not hasattr(self._atv.remote_control, single_command):
|
||||
continue
|
||||
|
||||
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:
|
||||
if not hasattr(self._atv.remote_control, single_command):
|
||||
continue
|
||||
|
||||
await getattr(self._atv.remote_control, single_command)()
|
||||
|
||||
return _send_commands()
|
||||
await getattr(self._atv.remote_control, single_command)()
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"domain": "apprise",
|
||||
"name": "Apprise",
|
||||
"documentation": "https://www.home-assistant.io/components/apprise",
|
||||
"requirements": ["apprise==0.8.2"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/apprise",
|
||||
"requirements": ["apprise==0.8.3"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@caronc"]
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Support for the Asterisk Voicemail interface."""
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from asterisk_mbox import ServerError
|
||||
@ -55,7 +56,9 @@ class AsteriskMailbox(Mailbox):
|
||||
|
||||
client = self.hass.data[ASTERISK_DOMAIN].client
|
||||
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:
|
||||
raise StreamError(err)
|
||||
|
||||
@ -63,9 +66,9 @@ class AsteriskMailbox(Mailbox):
|
||||
"""Return a list of the current messages."""
|
||||
return self.hass.data[ASTERISK_DOMAIN].messages
|
||||
|
||||
def async_delete(self, msgid):
|
||||
async def async_delete(self, msgid):
|
||||
"""Delete the specified messages."""
|
||||
client = self.hass.data[ASTERISK_DOMAIN].client
|
||||
_LOGGER.info("Deleting: %s", msgid)
|
||||
client.delete(msgid)
|
||||
await self.hass.async_add_executor_job(client.delete, msgid)
|
||||
return True
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Support for aurora forecast data sensor."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from math import floor
|
||||
|
||||
from aiohttp.hdrs import USER_AGENT
|
||||
import requests
|
||||
@ -99,8 +100,6 @@ class AuroraData:
|
||||
"""Initialize the data object."""
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.number_of_latitude_intervals = 513
|
||||
self.number_of_longitude_intervals = 1024
|
||||
self.headers = {USER_AGENT: HA_USER_AGENT}
|
||||
self.threshold = int(threshold)
|
||||
self.is_visible = None
|
||||
@ -126,18 +125,22 @@ class AuroraData:
|
||||
def get_aurora_forecast(self):
|
||||
"""Get forecast data and parse for given long/lat."""
|
||||
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 = [
|
||||
row.strip(" ").split(" ")
|
||||
row.strip().split()
|
||||
for row in raw_data.split("\n")
|
||||
if not row.startswith("#")
|
||||
]
|
||||
|
||||
# Convert lat and long for data points in table
|
||||
converted_latitude = round(
|
||||
(self.latitude / 180) * self.number_of_latitude_intervals
|
||||
)
|
||||
converted_longitude = round(
|
||||
(self.longitude / 360) * self.number_of_longitude_intervals
|
||||
# Assumes self.latitude belongs to [-90;90[ (South to North)
|
||||
# Assumes self.longitude belongs to [-180;180[ (West to East)
|
||||
# No assumptions made regarding the number of rows and columns
|
||||
converted_latitude = floor((self.latitude + 90) * len(forecast_table) / 180)
|
||||
converted_longitude = floor(
|
||||
(self.longitude + 180) * len(forecast_table[converted_latitude]) / 360
|
||||
)
|
||||
|
||||
return forecast_table[converted_latitude][converted_longitude]
|
||||
|
@ -25,8 +25,8 @@
|
||||
},
|
||||
"step": {
|
||||
"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",
|
||||
"title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u5169\u6b65\u9a5f\u9a57\u8b49"
|
||||
"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\u96d9\u91cd\u9a57\u8b49"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
|
@ -1,17 +1,19 @@
|
||||
"""Allow to set up simple automation rules via the config file."""
|
||||
import asyncio
|
||||
from functools import partial
|
||||
import importlib
|
||||
import logging
|
||||
from typing import Any, Awaitable, Callable
|
||||
from typing import Any, Awaitable, Callable, List, Optional, Set
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import sun
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_NAME,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ID,
|
||||
CONF_PLATFORM,
|
||||
CONF_ZONE,
|
||||
EVENT_AUTOMATION_TRIGGERED,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
SERVICE_RELOAD,
|
||||
@ -20,11 +22,10 @@ from homeassistant.const import (
|
||||
SERVICE_TURN_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.helpers import condition, extract_domain_configs, script
|
||||
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_component import EntityComponent
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
@ -93,29 +94,23 @@ _TRIGGER_SCHEMA = vol.All(
|
||||
|
||||
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
# str on purpose
|
||||
CONF_ID: str,
|
||||
CONF_ALIAS: cv.string,
|
||||
vol.Optional(CONF_DESCRIPTION): cv.string,
|
||||
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
|
||||
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
|
||||
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
|
||||
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
|
||||
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
cv.deprecated(CONF_HIDE_ENTITY, invalidation_version="0.107"),
|
||||
vol.Schema(
|
||||
{
|
||||
# str on purpose
|
||||
CONF_ID: str,
|
||||
CONF_ALIAS: cv.string,
|
||||
vol.Optional(CONF_DESCRIPTION): cv.string,
|
||||
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
|
||||
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
|
||||
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
|
||||
vol.Optional(CONF_CONDITION): _CONDITION_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
|
||||
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)
|
||||
|
||||
|
||||
@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):
|
||||
"""Set up the automation."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
hass.data[DOMAIN] = component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
|
||||
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."""
|
||||
tasks = []
|
||||
for entity in await component.async_extract_from_service(service_call):
|
||||
tasks.append(
|
||||
entity.async_trigger(
|
||||
service_call.data[ATTR_VARIABLES],
|
||||
skip_condition=service_call.data[CONF_SKIP_CONDITION],
|
||||
context=service_call.context,
|
||||
)
|
||||
)
|
||||
await entity.async_trigger(
|
||||
service_call.data[ATTR_VARIABLES],
|
||||
skip_condition=service_call.data[CONF_SKIP_CONDITION],
|
||||
context=service_call.context,
|
||||
)
|
||||
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
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)
|
||||
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")
|
||||
component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on")
|
||||
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
|
||||
|
||||
async def reload_service_handler(service_call):
|
||||
"""Remove all automations and load new ones from config."""
|
||||
@ -177,33 +221,10 @@ async def async_setup(hass, config):
|
||||
return
|
||||
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(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_RELOAD,
|
||||
reload_service_handler,
|
||||
schema=RELOAD_SERVICE_SCHEMA,
|
||||
hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.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
|
||||
|
||||
|
||||
@ -214,29 +235,36 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
self,
|
||||
automation_id,
|
||||
name,
|
||||
async_attach_triggers,
|
||||
trigger_config,
|
||||
cond_func,
|
||||
async_action,
|
||||
action_script,
|
||||
hidden,
|
||||
initial_state,
|
||||
):
|
||||
"""Initialize an automation entity."""
|
||||
self._id = automation_id
|
||||
self._name = name
|
||||
self._async_attach_triggers = async_attach_triggers
|
||||
self._trigger_config = trigger_config
|
||||
self._async_detach_triggers = None
|
||||
self._cond_func = cond_func
|
||||
self._async_action = async_action
|
||||
self.action_script = action_script
|
||||
self._last_triggered = None
|
||||
self._hidden = hidden
|
||||
self._initial_state = initial_state
|
||||
self._is_enabled = False
|
||||
self._referenced_entities: Optional[Set[str]] = None
|
||||
self._referenced_devices: Optional[Set[str]] = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the automation."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID."""
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed for automation entities."""
|
||||
@ -257,6 +285,45 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
"""Return True if entity is on."""
|
||||
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:
|
||||
"""Startup with initial state or previous state."""
|
||||
await super().async_added_to_hass()
|
||||
@ -307,7 +374,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
|
||||
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
|
||||
|
||||
# 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},
|
||||
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()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@ -341,9 +421,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
|
||||
# HomeAssistant is starting up
|
||||
if self.hass.state != CoreState.not_running:
|
||||
self._async_detach_triggers = await self._async_attach_triggers(
|
||||
self.async_trigger
|
||||
)
|
||||
self._async_detach_triggers = await self._async_attach_triggers()
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
@ -353,9 +431,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
if not self._is_enabled or self._async_detach_triggers is not None:
|
||||
return
|
||||
|
||||
self._async_detach_triggers = await self._async_attach_triggers(
|
||||
self.async_trigger
|
||||
)
|
||||
self._async_detach_triggers = await self._async_attach_triggers()
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, async_enable_automation
|
||||
@ -375,6 +451,38 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
|
||||
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
|
||||
def device_state_attributes(self):
|
||||
"""Return automation attributes."""
|
||||
@ -401,7 +509,7 @@ async def _async_process_config(hass, config, component):
|
||||
hidden = config_block[CONF_HIDE_ENTITY]
|
||||
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:
|
||||
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:
|
||||
continue
|
||||
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(
|
||||
automation_id,
|
||||
name,
|
||||
async_attach_triggers,
|
||||
config_block[CONF_TRIGGER],
|
||||
cond_func,
|
||||
action,
|
||||
action_script,
|
||||
hidden,
|
||||
initial_state,
|
||||
)
|
||||
@ -437,27 +535,9 @@ async def _async_process_config(hass, config, component):
|
||||
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):
|
||||
"""Process if checks."""
|
||||
if_configs = p_config.get(CONF_CONDITION)
|
||||
if_configs = p_config[CONF_CONDITION]
|
||||
|
||||
checks = []
|
||||
for if_config in if_configs:
|
||||
@ -471,35 +551,33 @@ async def _async_process_if(hass, config, p_config):
|
||||
"""AND all conditions."""
|
||||
return all(check(hass, variables) for check in checks)
|
||||
|
||||
if_action.config = if_configs
|
||||
|
||||
return if_action
|
||||
|
||||
|
||||
async def _async_process_trigger(hass, config, trigger_configs, name, action):
|
||||
"""Set up the triggers.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
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:
|
||||
@callback
|
||||
def _trigger_extract_device(trigger_conf: dict) -> Optional[str]:
|
||||
"""Extract devices from a trigger config."""
|
||||
if trigger_conf[CONF_PLATFORM] != "device":
|
||||
return None
|
||||
|
||||
def remove_triggers():
|
||||
"""Remove attached triggers."""
|
||||
for remove in removes:
|
||||
remove()
|
||||
return trigger_conf[CONF_DEVICE_ID]
|
||||
|
||||
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 []
|
||||
|
@ -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_released(number, released)
|
||||
|
||||
@callback
|
||||
def async_remove():
|
||||
"""Remove all subscriptions used for this trigger."""
|
||||
return
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "aws",
|
||||
"name": "Amazon Web Services (AWS)",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aws",
|
||||
"requirements": ["aiobotocore==0.10.4"],
|
||||
"requirements": ["aiobotocore==0.11.1"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@awarecan", "@robbiet480"]
|
||||
}
|
||||
|
@ -4,7 +4,8 @@
|
||||
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
|
||||
"bad_config_file": "Mauvaises donn\u00e9es du fichier de configuration",
|
||||
"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": {
|
||||
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
|
||||
|
@ -59,11 +59,11 @@
|
||||
"moving": "{entity_name} ha iniziato a muoversi",
|
||||
"no_gas": "{entity_name} ha smesso la rilevazione di gas",
|
||||
"no_light": "{entity_name} smesso il rilevamento di luce",
|
||||
"no_motion": "{nome_entit\u00e0} ha smesso di rilevare il movimento",
|
||||
"no_problem": "{nome_entit\u00e0} ha smesso di rilevare un problema",
|
||||
"no_motion": "{entity_name} ha smesso di rilevare il movimento",
|
||||
"no_problem": "{entity_name} ha smesso di rilevare un problema",
|
||||
"no_smoke": "{entity_name} ha smesso la rilevazione di fumo",
|
||||
"no_sound": "{nome_entit\u00e0} ha smesso di rilevare il suono",
|
||||
"no_vibration": "{nome_entit\u00e0} ha smesso di rilevare le vibrazioni",
|
||||
"no_sound": "{entity_name} ha smesso di rilevare il suono",
|
||||
"no_vibration": "{entity_name} ha smesso di rilevare le vibrazioni",
|
||||
"not_bat_low": "{entity_name} batteria normale",
|
||||
"not_cold": "{entity_name} non \u00e8 diventato freddo",
|
||||
"not_connected": "{entity_name} \u00e8 disconnesso",
|
||||
|
@ -5,7 +5,7 @@ import voluptuous as vol
|
||||
|
||||
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.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import condition, config_validation as cv
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
async_entries_for_device,
|
||||
@ -232,6 +232,7 @@ async def async_get_conditions(
|
||||
return conditions
|
||||
|
||||
|
||||
@callback
|
||||
def async_condition_from_config(
|
||||
config: ConfigType, config_validation: bool
|
||||
) -> condition.ConditionCheckerType:
|
||||
|
@ -10,6 +10,7 @@ import socket
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
@ -64,67 +65,67 @@ SERVICE_SEND_SCHEMA = vol.Schema(
|
||||
SERVICE_LEARN_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_service(hass, host, device):
|
||||
"""Register a device for given host for use in services."""
|
||||
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):
|
||||
"""Learn a packet from remote."""
|
||||
device = hass.data[DOMAIN][call.data[CONF_HOST]]
|
||||
async def _learn_command(call):
|
||||
"""Learn a packet from remote."""
|
||||
device = hass.data[DOMAIN][call.data[CONF_HOST]]
|
||||
|
||||
try:
|
||||
auth = await hass.async_add_executor_job(device.auth)
|
||||
except socket.timeout:
|
||||
_LOGGER.error("Failed to connect to device, timeout")
|
||||
try:
|
||||
auth = await hass.async_add_executor_job(device.auth)
|
||||
except socket.timeout:
|
||||
_LOGGER.error("Failed to connect to device, timeout")
|
||||
return
|
||||
if not auth:
|
||||
_LOGGER.error("Failed to connect to device")
|
||||
return
|
||||
|
||||
await hass.async_add_executor_job(device.enter_learning)
|
||||
|
||||
_LOGGER.info("Press the key you want Home Assistant to learn")
|
||||
start_time = utcnow()
|
||||
while (utcnow() - start_time) < timedelta(seconds=20):
|
||||
packet = await hass.async_add_executor_job(device.check_data)
|
||||
if packet:
|
||||
data = b64encode(packet).decode("utf8")
|
||||
log_msg = f"Received packet is: {data}"
|
||||
_LOGGER.info(log_msg)
|
||||
hass.components.persistent_notification.async_create(
|
||||
log_msg, title="Broadlink switch"
|
||||
)
|
||||
return
|
||||
if not auth:
|
||||
_LOGGER.error("Failed to connect to device")
|
||||
return
|
||||
|
||||
await hass.async_add_executor_job(device.enter_learning)
|
||||
|
||||
_LOGGER.info("Press the key you want Home Assistant to learn")
|
||||
start_time = utcnow()
|
||||
while (utcnow() - start_time) < timedelta(seconds=20):
|
||||
packet = await hass.async_add_executor_job(device.check_data)
|
||||
if packet:
|
||||
data = b64encode(packet).decode("utf8")
|
||||
log_msg = f"Received packet is: {data}"
|
||||
_LOGGER.info(log_msg)
|
||||
hass.components.persistent_notification.async_create(
|
||||
log_msg, title="Broadlink switch"
|
||||
)
|
||||
return
|
||||
await asyncio.sleep(1)
|
||||
_LOGGER.error("No signal was received")
|
||||
hass.components.persistent_notification.async_create(
|
||||
"No signal was received", title="Broadlink switch"
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_LEARN, _learn_command, schema=SERVICE_LEARN_SCHEMA
|
||||
await asyncio.sleep(1)
|
||||
_LOGGER.error("No signal was received")
|
||||
hass.components.persistent_notification.async_create(
|
||||
"No signal was received", title="Broadlink switch"
|
||||
)
|
||||
|
||||
if not hass.services.has_service(DOMAIN, SERVICE_SEND):
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_LEARN, _learn_command, schema=SERVICE_LEARN_SCHEMA
|
||||
)
|
||||
|
||||
async def _send_packet(call):
|
||||
"""Send a packet."""
|
||||
device = hass.data[DOMAIN][call.data[CONF_HOST]]
|
||||
packets = call.data[CONF_PACKET]
|
||||
for packet in packets:
|
||||
for retry in range(DEFAULT_RETRY):
|
||||
async def _send_packet(call):
|
||||
"""Send a packet."""
|
||||
device = hass.data[DOMAIN][call.data[CONF_HOST]]
|
||||
packets = call.data[CONF_PACKET]
|
||||
for packet in packets:
|
||||
for retry in range(DEFAULT_RETRY):
|
||||
try:
|
||||
await hass.async_add_executor_job(device.send_data, packet)
|
||||
break
|
||||
except (socket.timeout, ValueError):
|
||||
try:
|
||||
await hass.async_add_executor_job(device.send_data, packet)
|
||||
break
|
||||
except (socket.timeout, ValueError):
|
||||
try:
|
||||
await hass.async_add_executor_job(device.auth)
|
||||
except socket.timeout:
|
||||
if retry == DEFAULT_RETRY - 1:
|
||||
_LOGGER.error("Failed to send packet to device")
|
||||
await hass.async_add_executor_job(device.auth)
|
||||
except socket.timeout:
|
||||
if retry == DEFAULT_RETRY - 1:
|
||||
_LOGGER.error("Failed to send packet to device")
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SEND, _send_packet, schema=SERVICE_SEND_SCHEMA
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SEND, _send_packet, schema=SERVICE_SEND_SCHEMA
|
||||
)
|
||||
|
14
homeassistant/components/brother/.translations/cs.json
Normal file
14
homeassistant/components/brother/.translations/cs.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
"snmp_error": "SNMP-server er sl\u00e5et fra, eller printeren underst\u00f8ttes ikke.",
|
||||
"wrong_host": "Ugyldigt v\u00e6rtsnavn eller IP-adresse."
|
||||
},
|
||||
"flow_title": "Brother-printer: {model} {serial_number}",
|
||||
"step": {
|
||||
"user": {
|
||||
"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",
|
||||
"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"
|
||||
|
24
homeassistant/components/brother/.translations/de.json
Normal file
24
homeassistant/components/brother/.translations/de.json
Normal 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"
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
"snmp_error": "SNMP server turned off or printer not supported.",
|
||||
"wrong_host": "Invalid hostname or IP address."
|
||||
},
|
||||
"flow_title": "Brother Printer: {model} {serial_number}",
|
||||
"step": {
|
||||
"user": {
|
||||
"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",
|
||||
"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"
|
||||
|
@ -9,6 +9,7 @@
|
||||
"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."
|
||||
},
|
||||
"flow_title": "Impresora Brother: {model} {serial_number}",
|
||||
"step": {
|
||||
"user": {
|
||||
"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",
|
||||
"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"
|
||||
|
29
homeassistant/components/brother/.translations/fr.json
Normal file
29
homeassistant/components/brother/.translations/fr.json
Normal 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"
|
||||
}
|
||||
}
|
15
homeassistant/components/brother/.translations/hu.json
Normal file
15
homeassistant/components/brother/.translations/hu.json
Normal 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"
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
"snmp_error": "Server SNMP spento o stampante non supportata.",
|
||||
"wrong_host": "Nome host o indirizzo IP non valido."
|
||||
},
|
||||
"flow_title": "Stampante Brother: {model} {serial_number}",
|
||||
"step": {
|
||||
"user": {
|
||||
"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",
|
||||
"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"
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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": {
|
||||
"user": {
|
||||
"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",
|
||||
"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"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user