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

View File

@ -6,7 +6,6 @@ omit =
homeassistant/helpers/signal.py
homeassistant/helpers/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

View File

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

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

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

View File

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

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

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

View File

@ -1,35 +1,109 @@
## Breaking Change:
<!-- 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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}

View 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

View File

@ -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:

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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"]
}

View File

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

View File

@ -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]

View File

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

View File

@ -1,8 +1,8 @@
{
"config": {
"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."
},

View File

@ -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

View File

@ -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:

View File

@ -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."""

View File

@ -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.",

View File

@ -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",

View File

@ -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.",

View File

@ -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.",

View File

@ -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.",

View File

@ -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",

View File

@ -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.",

View File

@ -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",

View File

@ -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.",

View File

@ -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.",

View File

@ -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.",

View File

@ -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",

View File

@ -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)

View File

@ -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):

View File

@ -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"

View File

@ -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)

View File

@ -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."
}
}
}

View File

@ -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",

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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": []
}

View File

@ -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()

View File

@ -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}

View File

@ -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.

View File

@ -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)

View File

@ -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()

View File

@ -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"
}

View File

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

View File

@ -6,6 +6,10 @@
"missing_configuration": "Tjek venligst dokumentationen om, hvordan man indstiller Almond."
},
"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"
}

View File

@ -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": {

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -6,6 +6,10 @@
"missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694."
},
"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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -6,6 +6,10 @@
"missing_configuration": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 Almond."
},
"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"
}

View File

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

View File

@ -6,6 +6,10 @@
"missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002"
},
"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"
}

View File

@ -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": {

View File

@ -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]

View File

@ -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]

View File

@ -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

View File

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

View File

@ -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": [],

View File

@ -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

View File

@ -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(

View File

@ -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 [

View File

@ -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)

View File

@ -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)()

View File

@ -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"]
}

View File

@ -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

View File

@ -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]

View File

@ -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"

View File

@ -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 []

View File

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

View File

@ -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"]
}

View File

@ -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",

View File

@ -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",

View File

@ -5,7 +5,7 @@ import voluptuous as vol
from homeassistant.components.device_automation.const import CONF_IS_OFF, CONF_IS_ON
from homeassistant.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:

View File

@ -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
)

View File

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

View File

@ -9,6 +9,7 @@
"snmp_error": "SNMP-server er sl\u00e5et fra, eller printeren underst\u00f8ttes ikke.",
"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"

View File

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

View File

@ -9,6 +9,7 @@
"snmp_error": "SNMP server turned off or printer not supported.",
"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"

View File

@ -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"

View File

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

View File

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

View File

@ -9,6 +9,7 @@
"snmp_error": "Server SNMP spento o stampante non supportata.",
"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"

View File

@ -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