diff --git a/.circleci/config.yml b/.circleci/config.yml
index c8d7c7ee6b2..e424f4c42cb 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -57,6 +57,7 @@ commands:
<<# parameters.all >>pip install -q --progress-bar off -r requirements_all.txt -c homeassistant/package_constraints.txt< parameters.all>>
<<# parameters.test >>pip install -q --progress-bar off -r requirements_test.txt -c homeassistant/package_constraints.txt< parameters.test>>
<<# parameters.test_all >>pip install -q --progress-bar off -r requirements_test_all.txt -c homeassistant/package_constraints.txt< parameters.test_all>>
+ no_output_timeout: 15m
- save_cache:
paths:
- ./venv
@@ -99,6 +100,13 @@ jobs:
mypy $TYPING_FILES
- install
+
+ - run:
+ name: validate manifests
+ command: |
+ . venv/bin/activate
+ python -m script.hassfest validate
+
- run:
name: run gen_requirements_all
command: |
@@ -139,6 +147,7 @@ jobs:
. venv/bin/activate
PYFILES=$(circleci tests glob "homeassistant/**/*.py" | circleci tests split)
pylint ${PYFILES}
+ no_output_timeout: 15m
pre-test:
parameters:
@@ -173,13 +182,14 @@ jobs:
- install
- run:
- name: run tests
+ name: run tests with code coverage
command: |
. venv/bin/activate
+ CC_SWITCH="--cov --cov-report="
TESTFILES=$(circleci tests glob "tests/**/test_*.py" | circleci tests split --split-by=timings)
- if [ -z "$CODE_COVERAGE" ]; then CC_SWITCH=""; else CC_SWITCH="--cov --cov-report html:htmlcov"; fi
- pytest --timeout=9 --duration=10 --junitxml=test-reports/homeassistant/results.xml -qq -o junit_family=xunit2 -o junit_suite_name=homeassistant -o console_output_style=count -p no:sugar $CC_SWITCH -- ${TESTFILES}
+ pytest --timeout=9 --durations=10 --junitxml=test-reports/homeassistant/results.xml -qq -o junit_family=xunit2 -o junit_suite_name=homeassistant -o console_output_style=count -p no:sugar $CC_SWITCH -- ${TESTFILES}
script/check_dirty
+ codecov
- store_test_results:
path: test-reports
diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 00000000000..9ad9083506d
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,15 @@
+codecov:
+ branch: dev
+coverage:
+ status:
+ project:
+ default:
+ target: 90
+ threshold: 0.09
+ notify:
+ # Notify codecov room in Discord. The webhook URL (encrypted below) ends in /slack which is why we configure a Slack notification.
+ slack:
+ default:
+ url: "secret:TgWDUM4Jw0w7wMJxuxNF/yhSOHglIo1fGwInJnRLEVPy2P2aLimkoK1mtKCowH5TFw+baUXVXT3eAqefbdvIuM8BjRR4aRji95C6CYyD0QHy4N8i7nn1SQkWDPpS8IthYTg07rUDF7s5guurkKv2RrgoCdnnqjAMSzHoExMOF7xUmblMdhBTWJgBpWEhASJy85w/xxjlsE1xoTkzeJu9Q67pTXtRcn+5kb5/vIzPSYg="
+comment:
+ require_changes: yes
diff --git a/.coveragerc b/.coveragerc
index 3cba8519314..86819ef51a3 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -21,14 +21,15 @@ omit =
homeassistant/components/alarmdecoder/*
homeassistant/components/alarmdotcom/alarm_control_panel.py
homeassistant/components/alpha_vantage/sensor.py
+ homeassistant/components/amazon_polly/tts.py
homeassistant/components/ambient_station/*
homeassistant/components/amcrest/*
+ homeassistant/components/ampio/*
homeassistant/components/android_ip_webcam/*
homeassistant/components/androidtv/*
homeassistant/components/anel_pwrctrl/switch.py
homeassistant/components/anthemav/media_player.py
homeassistant/components/apcupsd/*
- homeassistant/components/apiai/*
homeassistant/components/apple_tv/*
homeassistant/components/aqualogic/*
homeassistant/components/aquostv/media_player.py
@@ -45,9 +46,7 @@ omit =
homeassistant/components/august/*
homeassistant/components/automatic/device_tracker.py
homeassistant/components/avion/light.py
- homeassistant/components/aws_lambda/notify.py
- homeassistant/components/aws_sns/notify.py
- homeassistant/components/aws_sqs/notify.py
+ homeassistant/components/baidu/tts.py
homeassistant/components/bbb_gpio/*
homeassistant/components/bbox/device_tracker.py
homeassistant/components/bbox/sensor.py
@@ -64,6 +63,7 @@ omit =
homeassistant/components/bme280/sensor.py
homeassistant/components/bme680/sensor.py
homeassistant/components/bmw_connected_drive/*
+ homeassistant/components/bom/camera.py
homeassistant/components/bom/sensor.py
homeassistant/components/bom/weather.py
homeassistant/components/braviatv/media_player.py
@@ -84,6 +84,7 @@ omit =
homeassistant/components/channels/media_player.py
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
@@ -92,6 +93,7 @@ omit =
homeassistant/components/clicksend_tts/notify.py
homeassistant/components/cloudflare/*
homeassistant/components/cmus/media_player.py
+ homeassistant/components/co2signal/*
homeassistant/components/coinbase/*
homeassistant/components/comed_hourly_pricing/sensor.py
homeassistant/components/comfoconnect/*
@@ -125,7 +127,6 @@ omit =
homeassistant/components/dlink/switch.py
homeassistant/components/dlna_dmr/media_player.py
homeassistant/components/dnsip/sensor.py
- homeassistant/components/domain_expiry/sensor.py
homeassistant/components/dominos/*
homeassistant/components/doorbird/*
homeassistant/components/dovado/*
@@ -161,9 +162,12 @@ omit =
homeassistant/components/envisalink/*
homeassistant/components/ephember/climate.py
homeassistant/components/epson/media_player.py
+ homeassistant/components/epsonworkforce/sensor.py
homeassistant/components/eq3btsmart/climate.py
homeassistant/components/esphome/__init__.py
homeassistant/components/esphome/binary_sensor.py
+ homeassistant/components/esphome/camera.py
+ homeassistant/components/esphome/climate.py
homeassistant/components/esphome/cover.py
homeassistant/components/esphome/fan.py
homeassistant/components/esphome/light.py
@@ -203,6 +207,7 @@ omit =
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
homeassistant/components/gc100/*
+ homeassistant/components/geniushub/*
homeassistant/components/gearbest/sensor.py
homeassistant/components/geizhals/sensor.py
homeassistant/components/github/sensor.py
@@ -266,13 +271,10 @@ omit =
homeassistant/components/ifttt/*
homeassistant/components/iglo/light.py
homeassistant/components/ihc/*
- homeassistant/components/iliad_italy/sensor.py
homeassistant/components/imap/sensor.py
homeassistant/components/imap_email_content/sensor.py
homeassistant/components/influxdb/sensor.py
homeassistant/components/insteon/*
- homeassistant/components/insteon_local/*
- homeassistant/components/insteon_plm/*
homeassistant/components/ios/*
homeassistant/components/iota/*
homeassistant/components/iperf3/*
@@ -318,7 +320,10 @@ omit =
homeassistant/components/liveboxplaytv/media_player.py
homeassistant/components/llamalab_automate/notify.py
homeassistant/components/lockitron/lock.py
- homeassistant/components/logi_circle/*
+ homeassistant/components/logi_circle/__init__.py
+ homeassistant/components/logi_circle/camera.py
+ homeassistant/components/logi_circle/const.py
+ homeassistant/components/logi_circle/sensor.py
homeassistant/components/london_underground/sensor.py
homeassistant/components/loopenergy/sensor.py
homeassistant/components/luci/device_tracker.py
@@ -341,6 +346,7 @@ omit =
homeassistant/components/meteo_france/*
homeassistant/components/metoffice/sensor.py
homeassistant/components/metoffice/weather.py
+ homeassistant/components/microsoft/tts.py
homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/climate.py
@@ -363,8 +369,8 @@ omit =
homeassistant/components/mystrom/binary_sensor.py
homeassistant/components/mystrom/light.py
homeassistant/components/mystrom/switch.py
+ homeassistant/components/n26/*
homeassistant/components/nad/media_player.py
- homeassistant/components/nadtcp/media_player.py
homeassistant/components/nanoleaf/light.py
homeassistant/components/neato/*
homeassistant/components/nederlandse_spoorwegen/sensor.py
@@ -373,7 +379,6 @@ omit =
homeassistant/components/netatmo/*
homeassistant/components/netatmo_public/sensor.py
homeassistant/components/netdata/sensor.py
- homeassistant/components/netdata_public/sensor.py
homeassistant/components/netgear/device_tracker.py
homeassistant/components/netgear_lte/*
homeassistant/components/netio/switch.py
@@ -394,6 +399,7 @@ omit =
homeassistant/components/nzbget/sensor.py
homeassistant/components/octoprint/*
homeassistant/components/oem/climate.py
+ homeassistant/components/oasa_telematics/sensor.py
homeassistant/components/ohmconnect/sensor.py
homeassistant/components/onewire/sensor.py
homeassistant/components/onkyo/media_player.py
@@ -422,6 +428,7 @@ omit =
homeassistant/components/pencom/switch.py
homeassistant/components/philips_js/media_player.py
homeassistant/components/pi_hole/sensor.py
+ homeassistant/components/picotts/tts.py
homeassistant/components/piglow/light.py
homeassistant/components/pilight/*
homeassistant/components/ping/binary_sensor.py
@@ -533,6 +540,7 @@ omit =
homeassistant/components/sochain/sensor.py
homeassistant/components/socialblade/sensor.py
homeassistant/components/solaredge/sensor.py
+ homeassistant/components/somfy_mylink/*
homeassistant/components/sonarr/sensor.py
homeassistant/components/songpal/media_player.py
homeassistant/components/sonos/*
@@ -546,6 +554,7 @@ omit =
homeassistant/components/srp_energy/sensor.py
homeassistant/components/starlingbank/sensor.py
homeassistant/components/steam_online/sensor.py
+ homeassistant/components/stiebel_eltron/*
homeassistant/components/stride/notify.py
homeassistant/components/supervisord/sensor.py
homeassistant/components/swiss_hydrological_data/sensor.py
@@ -573,7 +582,6 @@ omit =
homeassistant/components/tellduslive/*
homeassistant/components/tellstick/*
homeassistant/components/telnet/switch.py
- homeassistant/components/telstra/notify.py
homeassistant/components/temper/sensor.py
homeassistant/components/tensorflow/image_processing.py
homeassistant/components/tesla/*
@@ -605,10 +613,6 @@ omit =
homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/*
homeassistant/components/travisci/sensor.py
- homeassistant/components/tts/amazon_polly.py
- homeassistant/components/tts/baidu.py
- homeassistant/components/tts/microsoft.py
- homeassistant/components/tts/picotts.py
homeassistant/components/tuya/*
homeassistant/components/twilio_call/notify.py
homeassistant/components/twilio_sms/notify.py
@@ -650,6 +654,7 @@ omit =
homeassistant/components/wirelesstag/*
homeassistant/components/worldtidesinfo/sensor.py
homeassistant/components/worxlandroid/sensor.py
+ homeassistant/components/wunderlist/*
homeassistant/components/x10/light.py
homeassistant/components/xbox_live/sensor.py
homeassistant/components/xeoma/camera.py
@@ -663,7 +668,7 @@ omit =
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
homeassistant/components/yamaha/media_player.py
homeassistant/components/yamaha_musiccast/media_player.py
- homeassistant/components/yeelight/light.py
+ homeassistant/components/yeelight/*
homeassistant/components/yeelightsunflower/light.py
homeassistant/components/yi/camera.py
homeassistant/components/zabbix/*
@@ -688,6 +693,7 @@ omit =
homeassistant/components/zigbee/*
homeassistant/components/ziggo_mediabox_xl/media_player.py
homeassistant/components/zoneminder/*
+ homeassistant/components/supla/*
homeassistant/components/zwave/util.py
[report]
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index ecdbddf5b5d..ebebf487275 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -23,7 +23,8 @@ 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)
If the code communicates with devices, web services, or third-party tools:
- - [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
+ - [ ] [_The manifest file_][manifest-docs] has all fields filled out correctly ([example][ex-manifest]).
+ - [ ] New dependencies have been added to `requirements` in the manifest ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
- [ ] New or updated dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New files were added to `.coveragerc`.
@@ -31,5 +32,7 @@ If the code communicates with devices, web services, or third-party tools:
If the code does not interact with devices:
- [ ] Tests have been added to verify that the new code works.
-[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard/__init__.py#L14
+[ex-manifest]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/mobile_app/manifest.json
+[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/mobile_app/manifest.json#L5
[ex-import]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard/__init__.py#L23
+[manifest-docs]: https://developers.home-assistant.io/docs/en/development_checklist.html#_the-manifest-file_
diff --git a/.github/main.workflow b/.github/main.workflow
index 54869682e1c..62336ebf126 100644
--- a/.github/main.workflow
+++ b/.github/main.workflow
@@ -1,41 +1,14 @@
-workflow "Python 3.7 - tox" {
- resolves = ["Python 3.7 - tests"]
- on = "push"
+workflow "Mention CODEOWNERS of integrations when integration label is added to an issue" {
+ on = "issues"
+ resolves = "codeowners-mention"
}
-action "Python 3.7 - tests" {
- uses = "home-assistant/actions/py37-tox@master"
- args = "-e py37"
+workflow "Mention CODEOWNERS of integrations when integration label is added to an PRs" {
+ on = "pull_request"
+ resolves = "codeowners-mention"
}
-workflow "Python 3.6 - tox" {
- resolves = ["Python 3.6 - tests"]
- on = "push"
-}
-
-action "Python 3.6 - tests" {
- uses = "home-assistant/actions/py36-tox@master"
- args = "-e py36"
-}
-
-workflow "Python 3.5 - tox" {
- resolves = ["Pyton 3.5 - typing"]
- on = "push"
-}
-
-action "Python 3.5 - tests" {
- uses = "home-assistant/actions/py35-tox@master"
- args = "-e py35"
-}
-
-action "Python 3.5 - lints" {
- uses = "home-assistant/actions/py35-tox@master"
- needs = ["Python 3.5 - tests"]
- args = "-e lint"
-}
-
-action "Pyton 3.5 - typing" {
- uses = "home-assistant/actions/py35-tox@master"
- args = "-e typing"
- needs = ["Python 3.5 - lints"]
+action "codeowners-mention" {
+ uses = "home-assistant/codeowners-mention@master"
+ secrets = ["GITHUB_TOKEN"]
}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0461d182232..00000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-sudo: false
-dist: xenial
-addons:
- apt:
- sources:
- - sourceline: "ppa:jonathonf/ffmpeg-4"
- packages:
- - libudev-dev
- - libavformat-dev
- - libavcodec-dev
- - libavdevice-dev
- - libavutil-dev
- - libswscale-dev
- - libswresample-dev
- - libavfilter-dev
-matrix:
- fast_finish: true
- include:
- - python: "3.5.3"
- env: TOXENV=lint
- - python: "3.5.3"
- env: TOXENV=pylint
- - python: "3.5.3"
- env: TOXENV=typing
- - python: "3.5.3"
- env: TOXENV=cov
- after_success: coveralls
- - python: "3.6"
- env: TOXENV=py36
- - python: "3.7"
- env: TOXENV=py37
- - python: "3.8-dev"
- env: TOXENV=py38
- if: branch = dev AND type = push
- allow_failures:
- - python: "3.8-dev"
- env: TOXENV=py38
-
-cache:
- directories:
- - $HOME/.cache/pip
-install: pip install -U tox coveralls
-language: python
-script: travis_wait 40 tox --develop
-services:
- - docker
-before_deploy:
- - docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21
-deploy:
- skip_cleanup: true
- provider: script
- script: script/travis_deploy
- on:
- branch: dev
- condition: $TOXENV = lint
diff --git a/CODEOWNERS b/CODEOWNERS
index e9d7a652a66..30269be9051 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,3 +1,4 @@
+# This file is generated by script/manifest/codeowners.py
# People marked here will be automatically requested for a review
# when the code that they own is touched.
# https://github.com/blog/2392-introducing-code-owners
@@ -7,285 +8,255 @@ setup.py @home-assistant/core
homeassistant/*.py @home-assistant/core
homeassistant/helpers/* @home-assistant/core
homeassistant/util/* @home-assistant/core
-homeassistant/components/api/* @home-assistant/core
-homeassistant/components/auth/* @home-assistant/core
-homeassistant/components/automation/* @home-assistant/core
-homeassistant/components/cloud/* @home-assistant/core
-homeassistant/components/config/* @home-assistant/core
-homeassistant/components/configurator/* @home-assistant/core
-homeassistant/components/conversation/* @home-assistant/core
-homeassistant/components/frontend/* @home-assistant/core
-homeassistant/components/group/* @home-assistant/core
-homeassistant/components/history/* @home-assistant/core
-homeassistant/components/http/* @home-assistant/core
-homeassistant/components/input_*/* @home-assistant/core
-homeassistant/components/introduction/* @home-assistant/core
-homeassistant/components/logger/* @home-assistant/core
-homeassistant/components/lovelace/* @home-assistant/core
-homeassistant/components/mqtt/* @home-assistant/core
-homeassistant/components/panel_custom/* @home-assistant/core
-homeassistant/components/panel_iframe/* @home-assistant/core
-homeassistant/components/onboarding/* @home-assistant/core
-homeassistant/components/persistent_notification/* @home-assistant/core
-homeassistant/components/scene/__init__.py @home-assistant/core
-homeassistant/components/scene/homeassistant.py @home-assistant/core
-homeassistant/components/script/* @home-assistant/core
-homeassistant/components/shell_command/* @home-assistant/core
-homeassistant/components/sun/* @home-assistant/core
-homeassistant/components/updater/* @home-assistant/core
-homeassistant/components/weblink/* @home-assistant/core
-homeassistant/components/websocket_api/* @home-assistant/core
-homeassistant/components/zone/* @home-assistant/core
-# Home Assistant Developer Teams
+# Virtualization
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
-homeassistant/components/zwave/* @home-assistant/z-wave
-homeassistant/components/*/zwave.py @home-assistant/z-wave
+# Other code
+homeassistant/scripts/check_config.py @kellerza
-homeassistant/components/hassio/* @home-assistant/hassio
-
-# Individual platforms
-homeassistant/components/notify/aws_lambda.py @robbiet480
-homeassistant/components/notify/aws_sns.py @robbiet480
-homeassistant/components/notify/aws_sqs.py @robbiet480
-homeassistant/components/notify/file.py @fabaff
-homeassistant/components/notify/flock.py @fabaff
-homeassistant/components/notify/gntp.py @robbiet480
-homeassistant/components/notify/html5.py @robbiet480
-homeassistant/components/notify/mastodon.py @fabaff
-homeassistant/components/notify/smtp.py @fabaff
-homeassistant/components/notify/syslog.py @fabaff
-homeassistant/components/notify/twilio_call.py @robbiet480
-homeassistant/components/notify/twilio_sms.py @robbiet480
-homeassistant/components/notify/xmpp.py @fabaff
-homeassistant/components/notify/yessssms.py @flowolf
-homeassistant/components/tts/amazon_polly.py @robbiet480
-
-# A
-homeassistant/components/airvisual/sensor.py @bachya
-homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
-homeassistant/components/alpha_vantage/sensor.py @fabaff
+# Integrations
+homeassistant/components/airvisual/* @bachya
+homeassistant/components/alarm_control_panel/* @colinodell
+homeassistant/components/alpha_vantage/* @fabaff
+homeassistant/components/amazon_polly/* @robbiet480
homeassistant/components/ambient_station/* @bachya
+homeassistant/components/api/* @home-assistant/core
homeassistant/components/arduino/* @fabaff
homeassistant/components/arest/* @fabaff
-homeassistant/components/asuswrt/device_tracker.py @kennedyshead
-homeassistant/components/automatic/device_tracker.py @armills
+homeassistant/components/asuswrt/* @kennedyshead
+homeassistant/components/auth/* @home-assistant/core
+homeassistant/components/automatic/* @armills
+homeassistant/components/automation/* @home-assistant/core
+homeassistant/components/aws/* @awarecan @robbiet480
homeassistant/components/axis/* @kane610
-
-# B
-homeassistant/components/bitcoin/sensor.py @fabaff
+homeassistant/components/bitcoin/* @fabaff
homeassistant/components/blink/* @fronzbot
homeassistant/components/bmw_connected_drive/* @ChristianKuehnel
-homeassistant/components/braviatv/media_player.py @robbiet480
+homeassistant/components/braviatv/* @robbiet480
homeassistant/components/broadlink/* @danielhiversen
-homeassistant/components/brunt/cover.py @eavanvalkenburg
-homeassistant/components/bt_smarthub/device_tracker.py @jxwolstenholme
-
-# C
+homeassistant/components/brunt/* @eavanvalkenburg
+homeassistant/components/bt_smarthub/* @jxwolstenholme
+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/core
homeassistant/components/cloudflare/* @ludeeus
-homeassistant/components/coolmaster/climate.py @OnFreund
+homeassistant/components/config/* @home-assistant/core
+homeassistant/components/configurator/* @home-assistant/core
+homeassistant/components/conversation/* @home-assistant/core
+homeassistant/components/coolmaster/* @OnFreund
homeassistant/components/counter/* @fabaff
-homeassistant/components/cover/group.py @cdce8p
-homeassistant/components/cpuspeed/sensor.py @fabaff
-homeassistant/components/cups/sensor.py @fabaff
-
-# D
+homeassistant/components/cover/* @home-assistant/core
+homeassistant/components/cpuspeed/* @fabaff
+homeassistant/components/cups/* @fabaff
homeassistant/components/daikin/* @fredrike @rofrantz
homeassistant/components/darksky/* @fabaff
-homeassistant/components/discogs/sensor.py @thibmaek
homeassistant/components/deconz/* @kane610
-homeassistant/components/demo/weather.py @fabaff
+homeassistant/components/demo/* @home-assistant/core
homeassistant/components/digital_ocean/* @fabaff
+homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7
homeassistant/components/dweet/* @fabaff
-
-# E
homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edp_redy/* @abmantis
-homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/egardia/* @jeroenterheerdt
-homeassistant/components/emby/media_player.py @mezz64
-homeassistant/components/ephember/climate.py @ttroy50
-homeassistant/components/eq3btsmart/climate.py @rytilahti
+homeassistant/components/eight_sleep/* @mezz64
+homeassistant/components/emby/* @mezz64
+homeassistant/components/enigma2/* @fbradyirl
+homeassistant/components/ephember/* @ttroy50
+homeassistant/components/epsonworkforce/* @ThaStealth
+homeassistant/components/eq3btsmart/* @rytilahti
homeassistant/components/esphome/* @OttoWinter
-
-# F
-homeassistant/components/file/sensor.py @fabaff
-homeassistant/components/filter/sensor.py @dgomes
-homeassistant/components/fitbit/sensor.py @robbiet480
-homeassistant/components/fixer/sensor.py @fabaff
-homeassistant/components/flunearyou/sensor.py @bachya
+homeassistant/components/file/* @fabaff
+homeassistant/components/filter/* @dgomes
+homeassistant/components/fitbit/* @robbiet480
+homeassistant/components/fixer/* @fabaff
+homeassistant/components/flock/* @fabaff
+homeassistant/components/flunearyou/* @bachya
homeassistant/components/foursquare/* @robbiet480
homeassistant/components/freebox/* @snoof85
-
-# G
-homeassistant/components/gearbest/sensor.py @HerrHofrat
-homeassistant/components/gitter/sensor.py @fabaff
-homeassistant/components/glances/sensor.py @fabaff
-homeassistant/components/google_travel_time/sensor.py @robbiet480
+homeassistant/components/frontend/* @home-assistant/core
+homeassistant/components/gearbest/* @HerrHofrat
+homeassistant/components/gitter/* @fabaff
+homeassistant/components/glances/* @fabaff
+homeassistant/components/gntp/* @robbiet480
+homeassistant/components/google_translate/* @awarecan
+homeassistant/components/google_travel_time/* @robbiet480
homeassistant/components/googlehome/* @ludeeus
-homeassistant/components/gpsd/sensor.py @fabaff
-homeassistant/components/gtfs/sensor.py @robbiet480
-
-# H
+homeassistant/components/gpsd/* @fabaff
+homeassistant/components/group/* @home-assistant/core
+homeassistant/components/gtfs/* @robbiet480
homeassistant/components/harmony/* @ehendrix23
-homeassistant/components/hikvision/binary_sensor.py @mezz64
+homeassistant/components/hassio/* @home-assistant/hass-io
+homeassistant/components/heos/* @andrewsayre
+homeassistant/components/hikvision/* @mezz64
+homeassistant/components/hikvisioncam/* @fbradyirl
+homeassistant/components/history/* @home-assistant/core
homeassistant/components/history_graph/* @andrey-git
homeassistant/components/hive/* @Rendili @KJonline
+homeassistant/components/homeassistant/* @home-assistant/core
homeassistant/components/homekit/* @cdce8p
+homeassistant/components/homematic/* @pvizeli @danielperna84
+homeassistant/components/html5/* @robbiet480
+homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop
-homeassistant/components/huawei_router/device_tracker.py @abmantis
-
-# I
+homeassistant/components/huawei_router/* @abmantis
+homeassistant/components/hue/* @balloob
+homeassistant/components/ign_sismologia/* @exxamalte
homeassistant/components/influxdb/* @fabaff
-homeassistant/components/integration/sensor.py @dgomes
+homeassistant/components/input_boolean/* @home-assistant/core
+homeassistant/components/input_datetime/* @home-assistant/core
+homeassistant/components/input_number/* @home-assistant/core
+homeassistant/components/input_select/* @home-assistant/core
+homeassistant/components/input_text/* @home-assistant/core
+homeassistant/components/integration/* @dgomes
homeassistant/components/ios/* @robbiet480
homeassistant/components/ipma/* @dgomes
-homeassistant/components/irish_rail_transport/sensor.py @ttroy50
-
-# J
-homeassistant/components/jewish_calendar/sensor.py @tsvi
-
-# K
+homeassistant/components/irish_rail_transport/* @ttroy50
+homeassistant/components/jewish_calendar/* @tsvi
homeassistant/components/knx/* @Julius2342
-homeassistant/components/kodi/media_player.py @armills
+homeassistant/components/kodi/* @armills
homeassistant/components/konnected/* @heythisisnate
-
-# L
-homeassistant/components/lametric/notify.py @robbiet480
-homeassistant/components/launch_library/sensor.py @ludeeus
+homeassistant/components/lametric/* @robbiet480
+homeassistant/components/launch_library/* @ludeeus
homeassistant/components/lifx/* @amelchio
-homeassistant/components/lifx_cloud/scene.py @amelchio
-homeassistant/components/lifx_legacy/light.py @amelchio
-homeassistant/components/linux_battery/sensor.py @fabaff
-homeassistant/components/liveboxplaytv/media_player.py @pschmitt
+homeassistant/components/lifx_cloud/* @amelchio
+homeassistant/components/lifx_legacy/* @amelchio
+homeassistant/components/linux_battery/* @fabaff
+homeassistant/components/liveboxplaytv/* @pschmitt
+homeassistant/components/logger/* @home-assistant/core
+homeassistant/components/logi_circle/* @evanjd
+homeassistant/components/lovelace/* @home-assistant/core
+homeassistant/components/luci/* @fbradyirl
homeassistant/components/luftdaten/* @fabaff
-
-# M
+homeassistant/components/mastodon/* @fabaff
homeassistant/components/matrix/* @tinloaf
-homeassistant/components/mediaroom/media_player.py @dgomes
+homeassistant/components/mediaroom/* @dgomes
homeassistant/components/melissa/* @kennedyshead
-homeassistant/components/met/weather.py @danielhiversen
-homeassistant/components/miflora/sensor.py @danielhiversen @ChristianKuehnel
-homeassistant/components/mill/climate.py @danielhiversen
-homeassistant/components/min_max/sensor.py @fabaff
+homeassistant/components/met/* @danielhiversen
+homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
+homeassistant/components/mill/* @danielhiversen
+homeassistant/components/min_max/* @fabaff
homeassistant/components/mobile_app/* @robbiet480
-homeassistant/components/monoprice/media_player.py @etsinko
-homeassistant/components/moon/sensor.py @fabaff
-homeassistant/components/mpd/media_player.py @fabaff
+homeassistant/components/monoprice/* @etsinko
+homeassistant/components/moon/* @fabaff
+homeassistant/components/mpd/* @fabaff
+homeassistant/components/mqtt/* @home-assistant/core
homeassistant/components/mystrom/* @fabaff
-
-# N
-homeassistant/components/nello/lock.py @pschmitt
+homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444
-homeassistant/components/netdata/sensor.py @fabaff
+homeassistant/components/nest/* @awarecan
+homeassistant/components/netdata/* @fabaff
homeassistant/components/nissan_leaf/* @filcole
-homeassistant/components/nmbs/sensor.py @thibmaek
+homeassistant/components/nmbs/* @thibmaek
homeassistant/components/no_ip/* @fabaff
-homeassistant/components/nuki/lock.py @pschmitt
-homeassistant/components/nsw_fuel_station/sensor.py @nickw444
-
-# O
-homeassistant/components/ohmconnect/sensor.py @robbiet480
+homeassistant/components/notify/* @flowolf
+homeassistant/components/nsw_fuel_station/* @nickw444
+homeassistant/components/nuki/* @pschmitt
+homeassistant/components/ohmconnect/* @robbiet480
+homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/openuv/* @bachya
-homeassistant/components/openweathermap/weather.py @fabaff
+homeassistant/components/openweathermap/* @fabaff
homeassistant/components/owlet/* @oblogic7
-
-# P
-homeassistant/components/pi_hole/sensor.py @fabaff
+homeassistant/components/panel_custom/* @home-assistant/core
+homeassistant/components/panel_iframe/* @home-assistant/core
+homeassistant/components/persistent_notification/* @home-assistant/core
+homeassistant/components/pi_hole/* @fabaff
homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/point/* @fredrike
-homeassistant/components/pollen/sensor.py @bachya
-homeassistant/components/push/camera.py @dgomes
-homeassistant/components/pvoutput/sensor.py @fabaff
-
-# Q
-homeassistant/components/qnap/sensor.py @colinodell
-homeassistant/components/quantum_gateway/device_tracker.py @cisasteelersfan
+homeassistant/components/pollen/* @bachya
+homeassistant/components/push/* @dgomes
+homeassistant/components/pvoutput/* @fabaff
+homeassistant/components/qnap/* @colinodell
+homeassistant/components/quantum_gateway/* @cisasteelersfan
homeassistant/components/qwikswitch/* @kellerza
-
-# R
+homeassistant/components/raincloud/* @vanstinator
homeassistant/components/rainmachine/* @bachya
homeassistant/components/random/* @fabaff
homeassistant/components/rfxtrx/* @danielhiversen
homeassistant/components/rmvtransport/* @cgtobi
-homeassistant/components/roomba/vacuum.py @pschmitt
-homeassistant/components/ruter/sensor.py @ludeeus
-
-# S
-homeassistant/components/scrape/sensor.py @fabaff
-homeassistant/components/sensibo/climate.py @andrey-git
-homeassistant/components/serial/sensor.py @fabaff
-homeassistant/components/seventeentrack/sensor.py @bachya
+homeassistant/components/roomba/* @pschmitt
+homeassistant/components/ruter/* @ludeeus
+homeassistant/components/scene/* @home-assistant/core
+homeassistant/components/scrape/* @fabaff
+homeassistant/components/script/* @home-assistant/core
+homeassistant/components/sensibo/* @andrey-git
+homeassistant/components/serial/* @fabaff
+homeassistant/components/seventeentrack/* @bachya
+homeassistant/components/shell_command/* @home-assistant/core
homeassistant/components/shiftr/* @fabaff
-homeassistant/components/shodan/sensor.py @fabaff
+homeassistant/components/shodan/* @fabaff
homeassistant/components/simplisafe/* @bachya
-homeassistant/components/sma/sensor.py @kellerza
+homeassistant/components/sma/* @kellerza
homeassistant/components/smartthings/* @andrewsayre
+homeassistant/components/smtp/* @fabaff
homeassistant/components/sonos/* @amelchio
homeassistant/components/spaceapi/* @fabaff
homeassistant/components/spider/* @peternijssen
-homeassistant/components/sql/sensor.py @dgomes
-homeassistant/components/statistics/sensor.py @fabaff
-homeassistant/components/swiss_*/* @fabaff
-homeassistant/components/switchbot/switch.py @danielhiversen
-homeassistant/components/switchmate/switch.py @danielhiversen
-homeassistant/components/synology_srm/device_tracker.py @aerialls
-homeassistant/components/sytadin/sensor.py @gautric
-
-# T
+homeassistant/components/sql/* @dgomes
+homeassistant/components/statistics/* @fabaff
+homeassistant/components/stiebel_eltron/* @fucm
+homeassistant/components/sun/* @home-assistant/core
+homeassistant/components/supla/* @mwegrzynek
+homeassistant/components/swiss_hydrological_data/* @fabaff
+homeassistant/components/swiss_public_transport/* @fabaff
+homeassistant/components/switchbot/* @danielhiversen
+homeassistant/components/switchmate/* @danielhiversen
+homeassistant/components/synology_srm/* @aerialls
+homeassistant/components/syslog/* @fabaff
+homeassistant/components/sytadin/* @gautric
homeassistant/components/tahoma/* @philklei
-homeassistant/components/tautulli/sensor.py @ludeeus
+homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike
-homeassistant/components/template/cover.py @PhracturedBlue
+homeassistant/components/template/* @PhracturedBlue
homeassistant/components/tesla/* @zabuldon
homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff
-homeassistant/components/threshold/binary_sensor.py @fabaff
+homeassistant/components/threshold/* @fabaff
homeassistant/components/tibber/* @danielhiversen
-homeassistant/components/tile/device_tracker.py @bachya
-homeassistant/components/time_date/sensor.py @fabaff
+homeassistant/components/tile/* @bachya
+homeassistant/components/time_date/* @fabaff
homeassistant/components/toon/* @frenck
homeassistant/components/tplink/* @rytilahti
-homeassistant/components/traccar/device_tracker.py @ludeeus
+homeassistant/components/traccar/* @ludeeus
homeassistant/components/tradfri/* @ggravlingen
-
-# U
-homeassistant/components/uber/sensor.py @robbiet480
+homeassistant/components/tts/* @robbiet480
+homeassistant/components/twilio_call/* @robbiet480
+homeassistant/components/twilio_sms/* @robbiet480
+homeassistant/components/uber/* @robbiet480
homeassistant/components/unifi/* @kane610
homeassistant/components/upcloud/* @scop
+homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @robbiet480
-homeassistant/components/uptimerobot/binary_sensor.py @ludeeus
+homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/utility_meter/* @dgomes
-
-# V
homeassistant/components/velux/* @Julius2342
-homeassistant/components/version/sensor.py @fabaff
-
-# W
-homeassistant/components/waqi/sensor.py @andrey-git
-homeassistant/components/weather/__init__.py @fabaff
+homeassistant/components/version/* @fabaff
+homeassistant/components/waqi/* @andrey-git
+homeassistant/components/weather/* @fabaff
+homeassistant/components/weblink/* @home-assistant/core
+homeassistant/components/websocket_api/* @home-assistant/core
homeassistant/components/wemo/* @sqldiablo
-homeassistant/components/worldclock/sensor.py @fabaff
-
-# X
-homeassistant/components/xfinity/device_tracker.py @cisasteelersfan
+homeassistant/components/worldclock/* @fabaff
+homeassistant/components/xfinity/* @cisasteelersfan
homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi
homeassistant/components/xiaomi_miio/* @rytilahti @syssi
-homeassistant/components/xiaomi_tv/media_player.py @fattdev
-
-# Y
+homeassistant/components/xiaomi_tv/* @fattdev
+homeassistant/components/xmpp/* @fabaff
homeassistant/components/yamaha_musiccast/* @jalmeroth
homeassistant/components/yeelight/* @rytilahti @zewelor
-homeassistant/components/yeelightsunflower/light.py @lindsaymarkward
-homeassistant/components/yi/camera.py @bachya
-
-# Z
+homeassistant/components/yeelightsunflower/* @lindsaymarkward
+homeassistant/components/yessssms/* @flowolf
+homeassistant/components/yi/* @bachya
homeassistant/components/zeroconf/* @robbiet480
homeassistant/components/zha/* @dmulcahey @adminiuga
+homeassistant/components/zone/* @home-assistant/core
homeassistant/components/zoneminder/* @rohankapoorcom
+homeassistant/components/zwave/* @home-assistant/z-wave
-# Other code
-homeassistant/scripts/check_config.py @kellerza
+# Individual files
+homeassistant/components/group/cover @cdce8p
+homeassistant/components/demo/weather @fabaff
diff --git a/Dockerfile b/Dockerfile
index aa9415fd1e0..98a45abf0ea 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,7 +27,7 @@ COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some dependencies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
- pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.11.3 cchardet cython tensorflow
+ pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.12.2 cchardet cython tensorflow
# Copy source
COPY . .
diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py
index 435ec317985..c2039161ceb 100644
--- a/homeassistant/bootstrap.py
+++ b/homeassistant/bootstrap.py
@@ -11,9 +11,6 @@ from typing import Any, Optional, Dict, Set
import voluptuous as vol
from homeassistant import core, config as conf_util, config_entries, loader
-from homeassistant.components import (
- persistent_notification, homeassistant as core_component
-)
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
from homeassistant.util.logging import AsyncHandler
@@ -29,50 +26,16 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
# hass.data key for logging information.
DATA_LOGGING = 'logging'
-LOGGING_COMPONENT = {'logger', 'system_log'}
-
-FIRST_INIT_COMPONENT = {
+CORE_INTEGRATIONS = ('homeassistant', 'persistent_notification')
+LOGGING_INTEGRATIONS = {'logger', 'system_log'}
+STAGE_1_INTEGRATIONS = {
+ # To record data
'recorder',
- 'mqtt',
+ # To make sure we forward data to other instances
'mqtt_eventstream',
- 'introduction',
- 'frontend',
- 'history',
}
-def from_config_dict(config: Dict[str, Any],
- hass: Optional[core.HomeAssistant] = None,
- 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) \
- -> Optional[core.HomeAssistant]:
- """Try to configure Home Assistant from a configuration dictionary.
-
- Dynamically loads required components and its dependencies.
- """
- if hass is None:
- hass = core.HomeAssistant()
- if config_dir is not None:
- config_dir = os.path.abspath(config_dir)
- hass.config.config_dir = config_dir
- if not is_virtual_env():
- hass.loop.run_until_complete(
- async_mount_local_lib_path(config_dir))
-
- # run task
- hass = hass.loop.run_until_complete(
- async_from_config_dict(
- config, hass, config_dir, enable_log, verbose, skip_pip,
- log_rotate_days, log_file, log_no_color)
- )
- return hass
-
-
async def async_from_config_dict(config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str] = None,
@@ -115,70 +78,17 @@ async def async_from_config_dict(config: Dict[str, Any],
"Further initialization aborted")
return None
- await hass.async_add_executor_job(
- conf_util.process_ha_config_upgrade, hass)
-
# Make a copy because we are mutating it.
config = OrderedDict(config)
# Merge packages
- conf_util.merge_packages_config(
+ 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()
- components = _get_components(hass, config)
-
- # Resolve all dependencies of all components.
- for component in list(components):
- try:
- components.update(loader.component_dependencies(hass, component))
- except loader.LoaderError:
- # Ignore it, or we'll break startup
- # It will be properly handled during setup.
- pass
-
- # setup components
- res = await core_component.async_setup(hass, config)
- if not res:
- _LOGGER.error("Home Assistant core failed to initialize. "
- "Further initialization aborted")
- return hass
-
- await persistent_notification.async_setup(hass, config)
-
- _LOGGER.info("Home Assistant core initialized")
-
- # stage 0, load logging components
- for component in components:
- if component in LOGGING_COMPONENT:
- hass.async_create_task(
- async_setup_component(hass, component, config))
-
- await hass.async_block_till_done()
-
- # Kick off loading the registries. They don't need to be awaited.
- asyncio.gather(
- hass.helpers.device_registry.async_get_registry(),
- hass.helpers.entity_registry.async_get_registry(),
- hass.helpers.area_registry.async_get_registry())
-
- # stage 1
- for component in components:
- if component in FIRST_INIT_COMPONENT:
- hass.async_create_task(
- async_setup_component(hass, component, config))
-
- await hass.async_block_till_done()
-
- # stage 2
- for component in components:
- if component in FIRST_INIT_COMPONENT or component in LOGGING_COMPONENT:
- continue
- hass.async_create_task(async_setup_component(hass, component, config))
-
- await hass.async_block_till_done()
+ await _async_set_up_integrations(hass, config)
stop = time()
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
@@ -231,32 +141,6 @@ async def async_from_config_dict(config: Dict[str, Any],
return hass
-def from_config_file(config_path: str,
- hass: Optional[core.HomeAssistant] = None,
- 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 if given,
- instantiates a new Home Assistant object if 'hass' is not given.
- """
- if hass is None:
- hass = core.HomeAssistant()
-
- # run task
- hass = hass.loop.run_until_complete(
- async_from_config_file(
- config_path, hass, verbose, skip_pip,
- log_rotate_days, log_file, log_no_color)
- )
-
- return hass
-
-
async def async_from_config_file(config_path: str,
hass: core.HomeAssistant,
verbose: bool = False,
@@ -280,6 +164,9 @@ async def async_from_config_file(config_path: str,
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)
@@ -398,18 +285,127 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
@core.callback
-def _get_components(hass: core.HomeAssistant,
- config: Dict[str, Any]) -> Set[str]:
- """Get components to set up."""
+def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
+ """Get domains of components to set up."""
# Filter out the repeating and common config section [homeassistant]
- components = set(key.split(' ')[0] for key in config.keys()
- if key != core.DOMAIN)
+ domains = set(key.split(' ')[0] for key in config.keys()
+ if key != core.DOMAIN)
# Add config entry domains
- components.update(hass.config_entries.async_domains()) # type: ignore
+ domains.update(hass.config_entries.async_domains()) # type: ignore
# Make sure the Hass.io component is loaded
if 'HASSIO' in os.environ:
- components.add('hassio')
+ domains.add('hassio')
- return components
+ return domains
+
+
+async def _async_set_up_integrations(
+ hass: core.HomeAssistant, config: Dict[str, Any]) -> None:
+ """Set up all the integrations."""
+ domains = _get_domains(hass, config)
+
+ # Resolve all dependencies of all components so we can find the logging
+ # and integrations that need faster initialization.
+ resolved_domains_task = asyncio.gather(*[
+ loader.async_component_dependencies(hass, domain)
+ for domain in domains
+ ], return_exceptions=True)
+
+ # Set up core.
+ _LOGGER.debug("Setting up %s", CORE_INTEGRATIONS)
+
+ if not all(await asyncio.gather(*[
+ async_setup_component(hass, domain, config)
+ for domain in CORE_INTEGRATIONS
+ ])):
+ _LOGGER.error("Home Assistant core failed to initialize. "
+ "Further initialization aborted")
+ return
+
+ _LOGGER.debug("Home Assistant core initialized")
+
+ # Finish resolving domains
+ for dep_domains in await resolved_domains_task:
+ # Result is either a set or an exception. We ignore exceptions
+ # It will be properly handled during setup of the domain.
+ if isinstance(dep_domains, set):
+ domains.update(dep_domains)
+
+ # setup components
+ logging_domains = domains & LOGGING_INTEGRATIONS
+ stage_1_domains = domains & STAGE_1_INTEGRATIONS
+ stage_2_domains = domains - logging_domains - stage_1_domains
+
+ if logging_domains:
+ _LOGGER.debug("Setting up %s", logging_domains)
+
+ await asyncio.gather(*[
+ async_setup_component(hass, domain, config)
+ for domain in logging_domains
+ ])
+
+ # Kick off loading the registries. They don't need to be awaited.
+ asyncio.gather(
+ hass.helpers.device_registry.async_get_registry(),
+ hass.helpers.entity_registry.async_get_registry(),
+ hass.helpers.area_registry.async_get_registry())
+
+ if stage_1_domains:
+ await asyncio.gather(*[
+ async_setup_component(hass, domain, config)
+ for domain in logging_domains
+ ])
+
+ # Load all integrations
+ after_dependencies = {} # type: Dict[str, Set[str]]
+
+ for int_or_exc in await asyncio.gather(*[
+ loader.async_get_integration(hass, domain)
+ for domain in stage_2_domains
+ ], return_exceptions=True):
+ # Exceptions are handled in async_setup_component.
+ if (isinstance(int_or_exc, loader.Integration) and
+ int_or_exc.after_dependencies):
+ after_dependencies[int_or_exc.domain] = set(
+ int_or_exc.after_dependencies
+ )
+
+ last_load = None
+ while stage_2_domains:
+ domains_to_load = set()
+
+ for domain in stage_2_domains:
+ after_deps = after_dependencies.get(domain)
+ # Load if integration has no after_dependencies or they are
+ # all loaded
+ if (not after_deps or
+ not after_deps-hass.config.components):
+ domains_to_load.add(domain)
+
+ if not domains_to_load or domains_to_load == last_load:
+ break
+
+ _LOGGER.debug("Setting up %s", domains_to_load)
+
+ await asyncio.gather(*[
+ async_setup_component(hass, domain, config)
+ for domain in domains_to_load
+ ])
+
+ last_load = domains_to_load
+ stage_2_domains -= domains_to_load
+
+ # These are stage 2 domains that never have their after_dependencies
+ # satisfied.
+ if stage_2_domains:
+ _LOGGER.debug("Final set up: %s", stage_2_domains)
+
+ await asyncio.gather(*[
+ async_setup_component(hass, domain, config)
+ for domain in stage_2_domains
+ ])
+
+ # Wrap up startup
+ await hass.async_block_till_done()
diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py
index 591bae1a9cf..3a64a5e31f0 100644
--- a/homeassistant/components/abode/__init__.py
+++ b/homeassistant/components/abode/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['abodepy==0.15.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by goabode.com"
diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py
index d7426e04166..d1d75b7417e 100644
--- a/homeassistant/components/abode/alarm_control_panel.py
+++ b/homeassistant/components/abode/alarm_control_panel.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
from . import ATTRIBUTION, DOMAIN as ABODE_DOMAIN, AbodeDevice
-DEPENDENCIES = ['abode']
-
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:security'
diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py
index 874723420ed..e3f74e9f4ec 100644
--- a/homeassistant/components/abode/binary_sensor.py
+++ b/homeassistant/components/abode/binary_sensor.py
@@ -7,8 +7,6 @@ from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['abode']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a sensor for an Abode device."""
diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py
index d37644eccc3..d0e4e833029 100644
--- a/homeassistant/components/abode/camera.py
+++ b/homeassistant/components/abode/camera.py
@@ -9,8 +9,6 @@ from homeassistant.util import Throttle
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
-DEPENDENCIES = ['abode']
-
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/abode/cover.py b/homeassistant/components/abode/cover.py
index c40159164dc..4c868daf4ba 100644
--- a/homeassistant/components/abode/cover.py
+++ b/homeassistant/components/abode/cover.py
@@ -5,8 +5,6 @@ from homeassistant.components.cover import CoverDevice
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
-DEPENDENCIES = ['abode']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py
index 9e88acce41f..6b3e5025c51 100644
--- a/homeassistant/components/abode/light.py
+++ b/homeassistant/components/abode/light.py
@@ -10,8 +10,6 @@ from homeassistant.util.color import (
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
-DEPENDENCIES = ['abode']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/abode/lock.py b/homeassistant/components/abode/lock.py
index 0f568a4ace2..c1272a3de5f 100644
--- a/homeassistant/components/abode/lock.py
+++ b/homeassistant/components/abode/lock.py
@@ -5,8 +5,6 @@ from homeassistant.components.lock import LockDevice
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
-DEPENDENCIES = ['abode']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/abode/manifest.json b/homeassistant/components/abode/manifest.json
new file mode 100644
index 00000000000..49e0c46fd55
--- /dev/null
+++ b/homeassistant/components/abode/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "abode",
+ "name": "Abode",
+ "documentation": "https://www.home-assistant.io/components/abode",
+ "requirements": [
+ "abodepy==0.15.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py
index ef6941c76d8..b7e8fc1a118 100644
--- a/homeassistant/components/abode/sensor.py
+++ b/homeassistant/components/abode/sensor.py
@@ -8,8 +8,6 @@ from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['abode']
-
# Sensor types: Name, icon
SENSOR_TYPES = {
'temp': ['Temperature', DEVICE_CLASS_TEMPERATURE],
diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py
index 3e3ce031855..74d1ea57bad 100644
--- a/homeassistant/components/abode/switch.py
+++ b/homeassistant/components/abode/switch.py
@@ -7,8 +7,6 @@ from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['abode']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Abode switch devices."""
diff --git a/homeassistant/components/acer_projector/manifest.json b/homeassistant/components/acer_projector/manifest.json
new file mode 100644
index 00000000000..4b8d6967491
--- /dev/null
+++ b/homeassistant/components/acer_projector/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "acer_projector",
+ "name": "Acer projector",
+ "documentation": "https://www.home-assistant.io/components/acer_projector",
+ "requirements": [
+ "pyserial==3.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py
index 7abb3d1edbc..242f3f4a009 100644
--- a/homeassistant/components/acer_projector/switch.py
+++ b/homeassistant/components/acer_projector/switch.py
@@ -1,9 +1,4 @@
-"""
-Use serial protocol of Acer projector to obtain state of the projector.
-
-For more details about this component, please refer to the documentation
-at https://home-assistant.io/components/switch.acer_projector/
-"""
+"""Use serial protocol of Acer projector to obtain state of the projector."""
import logging
import re
@@ -14,8 +9,6 @@ from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNKNOWN, CONF_NAME, CONF_FILENAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyserial==3.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_TIMEOUT = 'timeout'
diff --git a/homeassistant/components/actiontec/device_tracker.py b/homeassistant/components/actiontec/device_tracker.py
index 72d9992c60f..3f0c8786794 100644
--- a/homeassistant/components/actiontec/device_tracker.py
+++ b/homeassistant/components/actiontec/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Actiontec MI424WR (Verizon FIOS) routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.actiontec/
-"""
+"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
import logging
import re
import telnetlib
diff --git a/homeassistant/components/actiontec/manifest.json b/homeassistant/components/actiontec/manifest.json
new file mode 100644
index 00000000000..e233f430cfc
--- /dev/null
+++ b/homeassistant/components/actiontec/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "actiontec",
+ "name": "Actiontec",
+ "documentation": "https://www.home-assistant.io/components/actiontec",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py
index 1b90e645af4..ba49f8abd9a 100644
--- a/homeassistant/components/ads/__init__.py
+++ b/homeassistant/components/ads/__init__.py
@@ -4,14 +4,15 @@ import struct
import logging
import ctypes
from collections import namedtuple
+import asyncio
+import async_timeout
import voluptuous as vol
from homeassistant.const import (
CONF_DEVICE, CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['pyads==3.0.7']
+from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
@@ -31,6 +32,9 @@ CONF_ADS_VALUE = 'value'
CONF_ADS_VAR = 'adsvar'
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
+STATE_KEY_STATE = 'state'
+STATE_KEY_BRIGHTNESS = 'brightness'
+
DOMAIN = 'ads'
SERVICE_WRITE_DATA_BY_NAME = 'write_data_by_name'
@@ -154,28 +158,41 @@ class AdsHub:
def write_by_name(self, name, value, plc_datatype):
"""Write a value to the device."""
+ import pyads
with self._lock:
- return self._client.write_by_name(name, value, plc_datatype)
+ try:
+ return self._client.write_by_name(name, value, plc_datatype)
+ except pyads.ADSError as err:
+ _LOGGER.error("Error writing %s: %s", name, err)
def read_by_name(self, name, plc_datatype):
"""Read a value from the device."""
+ import pyads
with self._lock:
- return self._client.read_by_name(name, plc_datatype)
+ try:
+ return self._client.read_by_name(name, plc_datatype)
+ except pyads.ADSError as err:
+ _LOGGER.error("Error reading %s: %s", name, err)
def add_device_notification(self, name, plc_datatype, callback):
"""Add a notification to the ADS devices."""
- from pyads import NotificationAttrib
- attr = NotificationAttrib(ctypes.sizeof(plc_datatype))
+ import pyads
+ attr = pyads.NotificationAttrib(ctypes.sizeof(plc_datatype))
with self._lock:
- hnotify, huser = self._client.add_device_notification(
- name, attr, self._device_notification_callback)
- hnotify = int(hnotify)
- self._notification_items[hnotify] = NotificationItem(
- hnotify, huser, name, plc_datatype, callback)
+ try:
+ hnotify, huser = self._client.add_device_notification(
+ name, attr, self._device_notification_callback)
+ except pyads.ADSError as err:
+ _LOGGER.error("Error subscribing to %s: %s", name, err)
+ else:
+ hnotify = int(hnotify)
+ self._notification_items[hnotify] = NotificationItem(
+ hnotify, huser, name, plc_datatype, callback)
- _LOGGER.debug(
- "Added device notification %d for variable %s", hnotify, name)
+ _LOGGER.debug(
+ "Added device notification %d for variable %s",
+ hnotify, name)
def _device_notification_callback(self, notification, name):
"""Handle device notifications."""
@@ -210,3 +227,68 @@ class AdsHub:
_LOGGER.warning("No callback available for this datatype")
notification_item.callback(notification_item.name, value)
+
+
+class AdsEntity(Entity):
+ """Representation of ADS entity."""
+
+ def __init__(self, ads_hub, name, ads_var):
+ """Initialize ADS binary sensor."""
+ self._name = name
+ self._unique_id = ads_var
+ self._state_dict = {}
+ self._state_dict[STATE_KEY_STATE] = None
+ self._ads_hub = ads_hub
+ self._ads_var = ads_var
+ self._event = None
+
+ async def async_initialize_device(
+ self, ads_var, plctype, state_key=STATE_KEY_STATE, factor=None):
+ """Register device notification."""
+ def update(name, value):
+ """Handle device notifications."""
+ _LOGGER.debug('Variable %s changed its value to %d', name, value)
+
+ if factor is None:
+ self._state_dict[state_key] = value
+ else:
+ self._state_dict[state_key] = value / factor
+
+ asyncio.run_coroutine_threadsafe(async_event_set(), self.hass.loop)
+ self.schedule_update_ha_state()
+
+ async def async_event_set():
+ """Set event in async context."""
+ self._event.set()
+
+ self._event = asyncio.Event()
+
+ await self.hass.async_add_executor_job(
+ self._ads_hub.add_device_notification,
+ ads_var, plctype, update)
+ try:
+ with async_timeout.timeout(10):
+ await self._event.wait()
+ except asyncio.TimeoutError:
+ _LOGGER.debug('Variable %s: Timeout during first update',
+ ads_var)
+
+ @property
+ def name(self):
+ """Return the default name of the binary sensor."""
+ return self._name
+
+ @property
+ def unique_id(self):
+ """Return an unique identifier for this entity."""
+ return self._unique_id
+
+ @property
+ def should_poll(self):
+ """Return False because entity pushes its state to HA."""
+ return False
+
+ @property
+ def available(self):
+ """Return False if state has not been updated yet."""
+ return self._state_dict[STATE_KEY_STATE] is not None
diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py
index 3b935156d18..b6f3a54e437 100644
--- a/homeassistant/components/ads/binary_sensor.py
+++ b/homeassistant/components/ads/binary_sensor.py
@@ -8,13 +8,11 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
import homeassistant.helpers.config_validation as cv
-from . import CONF_ADS_VAR, DATA_ADS
+from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'ADS binary sensor'
-DEPENDENCIES = ['ads']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -34,51 +32,25 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities([ads_sensor])
-class AdsBinarySensor(BinarySensorDevice):
+class AdsBinarySensor(AdsEntity, BinarySensorDevice):
"""Representation of ADS binary sensors."""
def __init__(self, ads_hub, name, ads_var, device_class):
"""Initialize ADS binary sensor."""
- self._name = name
- self._unique_id = ads_var
- self._state = False
+ super().__init__(ads_hub, name, ads_var)
self._device_class = device_class or 'moving'
- self._ads_hub = ads_hub
- self.ads_var = ads_var
async def async_added_to_hass(self):
"""Register device notification."""
- def update(name, value):
- """Handle device notifications."""
- _LOGGER.debug('Variable %s changed its value to %d', name, value)
- self._state = value
- self.schedule_update_ha_state()
-
- self.hass.async_add_job(
- self._ads_hub.add_device_notification,
- self.ads_var, self._ads_hub.PLCTYPE_BOOL, update)
+ await self.async_initialize_device(self._ads_var,
+ self._ads_hub.PLCTYPE_BOOL)
@property
- def name(self):
- """Return the default name of the binary sensor."""
- return self._name
-
- @property
- def unique_id(self):
- """Return an unique identifier for this entity."""
- return self._unique_id
+ def is_on(self):
+ """Return True if the entity is on."""
+ return self._state_dict[STATE_KEY_STATE]
@property
def device_class(self):
"""Return the device class."""
return self._device_class
-
- @property
- def is_on(self):
- """Return if the binary sensor is on."""
- return self._state
-
- @property
- def should_poll(self):
- """Return False because entity pushes its state to HA."""
- return False
diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py
index c61fd813634..5168f49acdc 100644
--- a/homeassistant/components/ads/light.py
+++ b/homeassistant/components/ads/light.py
@@ -8,12 +8,11 @@ from homeassistant.components.light import (
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
-from . import CONF_ADS_VAR, CONF_ADS_VAR_BRIGHTNESS, DATA_ADS
+from . import CONF_ADS_VAR, CONF_ADS_VAR_BRIGHTNESS, DATA_ADS, \
+ AdsEntity, STATE_KEY_BRIGHTNESS, STATE_KEY_STATE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ads']
DEFAULT_NAME = 'ADS Light'
-CONF_ADSVAR_BRIGHTNESS = 'adsvar_brightness'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_ADS_VAR_BRIGHTNESS): cv.string,
@@ -30,91 +29,57 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
name = config.get(CONF_NAME)
add_entities([AdsLight(ads_hub, ads_var_enable, ads_var_brightness,
- name)], True)
+ name)])
-class AdsLight(Light):
+class AdsLight(AdsEntity, Light):
"""Representation of ADS light."""
def __init__(self, ads_hub, ads_var_enable, ads_var_brightness, name):
"""Initialize AdsLight entity."""
- self._ads_hub = ads_hub
- self._on_state = False
- self._brightness = None
- self._name = name
- self._unique_id = ads_var_enable
- self.ads_var_enable = ads_var_enable
- self.ads_var_brightness = ads_var_brightness
+ super().__init__(ads_hub, name, ads_var_enable)
+ self._state_dict[STATE_KEY_BRIGHTNESS] = None
+ self._ads_var_brightness = ads_var_brightness
async def async_added_to_hass(self):
"""Register device notification."""
- def update_on_state(name, value):
- """Handle device notifications for state."""
- _LOGGER.debug('Variable %s changed its value to %d', name, value)
- self._on_state = value
- self.schedule_update_ha_state()
+ await self.async_initialize_device(self._ads_var,
+ self._ads_hub.PLCTYPE_BOOL)
- def update_brightness(name, value):
- """Handle device notification for brightness."""
- _LOGGER.debug('Variable %s changed its value to %d', name, value)
- self._brightness = value
- self.schedule_update_ha_state()
-
- self.hass.async_add_executor_job(
- self._ads_hub.add_device_notification,
- self.ads_var_enable, self._ads_hub.PLCTYPE_BOOL, update_on_state
- )
- if self.ads_var_brightness is not None:
- self.hass.async_add_executor_job(
- self._ads_hub.add_device_notification,
- self.ads_var_brightness, self._ads_hub.PLCTYPE_INT,
- update_brightness
- )
-
- @property
- def name(self):
- """Return the name of the device if any."""
- return self._name
-
- @property
- def unique_id(self):
- """Return an unique identifier for this entity."""
- return self._unique_id
+ if self._ads_var_brightness is not None:
+ await self.async_initialize_device(self._ads_var_brightness,
+ self._ads_hub.PLCTYPE_UINT,
+ STATE_KEY_BRIGHTNESS)
@property
def brightness(self):
"""Return the brightness of the light (0..255)."""
- return self._brightness
-
- @property
- def is_on(self):
- """Return if light is on."""
- return self._on_state
-
- @property
- def should_poll(self):
- """Return False because entity pushes its state to HA."""
- return False
+ return self._state_dict[STATE_KEY_BRIGHTNESS]
@property
def supported_features(self):
"""Flag supported features."""
support = 0
- if self.ads_var_brightness is not None:
+ if self._ads_var_brightness is not None:
support = SUPPORT_BRIGHTNESS
return support
+ @property
+ def is_on(self):
+ """Return True if the entity is on."""
+ return self._state_dict[STATE_KEY_STATE]
+
def turn_on(self, **kwargs):
"""Turn the light on or set a specific dimmer value."""
brightness = kwargs.get(ATTR_BRIGHTNESS)
- self._ads_hub.write_by_name(self.ads_var_enable, True,
+ self._ads_hub.write_by_name(self._ads_var, True,
self._ads_hub.PLCTYPE_BOOL)
- if self.ads_var_brightness is not None and brightness is not None:
- self._ads_hub.write_by_name(self.ads_var_brightness, brightness,
+ if self._ads_var_brightness is not None and brightness is not None:
+ self._ads_hub.write_by_name(self._ads_var_brightness, brightness,
self._ads_hub.PLCTYPE_UINT)
def turn_off(self, **kwargs):
"""Turn the light off."""
- self._ads_hub.write_by_name(self.ads_var_enable, False,
+ self._ads_hub.write_by_name(self._ads_var, False,
self._ads_hub.PLCTYPE_BOOL)
diff --git a/homeassistant/components/ads/manifest.json b/homeassistant/components/ads/manifest.json
new file mode 100644
index 00000000000..0c759f0ad60
--- /dev/null
+++ b/homeassistant/components/ads/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ads",
+ "name": "Ads",
+ "documentation": "https://www.home-assistant.io/components/ads",
+ "requirements": [
+ "pyads==3.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py
index 118a669a7ad..415eea09ddf 100644
--- a/homeassistant/components/ads/sensor.py
+++ b/homeassistant/components/ads/sensor.py
@@ -7,15 +7,13 @@ from homeassistant.components import ads
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import Entity
-from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR
+from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, \
+ AdsEntity, STATE_KEY_STATE
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "ADS sensor"
-DEPENDENCIES = ['ads']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_ADS_FACTOR): cv.positive_int,
@@ -43,60 +41,31 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities([entity])
-class AdsSensor(Entity):
+class AdsSensor(AdsEntity):
"""Representation of an ADS sensor entity."""
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement,
factor):
"""Initialize AdsSensor entity."""
- self._ads_hub = ads_hub
- self._name = name
- self._unique_id = ads_var
- self._value = None
+ super().__init__(ads_hub, name, ads_var)
self._unit_of_measurement = unit_of_measurement
- self.ads_var = ads_var
- self.ads_type = ads_type
- self.factor = factor
+ self._ads_type = ads_type
+ self._factor = factor
async def async_added_to_hass(self):
"""Register device notification."""
- def update(name, value):
- """Handle device notifications."""
- _LOGGER.debug("Variable %s changed its value to %d", name, value)
-
- # If factor is set use it otherwise not
- if self.factor is None:
- self._value = value
- else:
- self._value = value / self.factor
- self.schedule_update_ha_state()
-
- self.hass.async_add_job(
- self._ads_hub.add_device_notification,
- self.ads_var, self._ads_hub.ADS_TYPEMAP[self.ads_type], update
- )
-
- @property
- def name(self):
- """Return the name of the entity."""
- return self._name
-
- @property
- def unique_id(self):
- """Return an unique identifier for this entity."""
- return self._unique_id
+ await self.async_initialize_device(
+ self._ads_var,
+ self._ads_hub.ADS_TYPEMAP[self._ads_type],
+ STATE_KEY_STATE,
+ self._factor)
@property
def state(self):
"""Return the state of the device."""
- return self._value
+ return self._state_dict[STATE_KEY_STATE]
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
-
- @property
- def should_poll(self):
- """Return False because entity pushes its state."""
- return False
diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py
index 094b4552349..23e130f2695 100644
--- a/homeassistant/components/ads/switch.py
+++ b/homeassistant/components/ads/switch.py
@@ -3,17 +3,14 @@ import logging
import voluptuous as vol
-from homeassistant.components.switch import PLATFORM_SCHEMA
+from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import ToggleEntity
-from . import CONF_ADS_VAR, DATA_ADS
+from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ads']
-
DEFAULT_NAME = 'ADS Switch'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -29,58 +26,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
name = config.get(CONF_NAME)
ads_var = config.get(CONF_ADS_VAR)
- add_entities([AdsSwitch(ads_hub, name, ads_var)], True)
+ add_entities([AdsSwitch(ads_hub, name, ads_var)])
-class AdsSwitch(ToggleEntity):
+class AdsSwitch(AdsEntity, SwitchDevice):
"""Representation of an ADS switch device."""
- def __init__(self, ads_hub, name, ads_var):
- """Initialize the AdsSwitch entity."""
- self._ads_hub = ads_hub
- self._on_state = False
- self._name = name
- self._unique_id = ads_var
- self.ads_var = ads_var
-
async def async_added_to_hass(self):
"""Register device notification."""
- def update(name, value):
- """Handle device notification."""
- _LOGGER.debug("Variable %s changed its value to %d", name, value)
- self._on_state = value
- self.schedule_update_ha_state()
-
- self.hass.async_add_job(
- self._ads_hub.add_device_notification,
- self.ads_var, self._ads_hub.PLCTYPE_BOOL, update)
+ await self.async_initialize_device(self._ads_var,
+ self._ads_hub.PLCTYPE_BOOL)
@property
def is_on(self):
- """Return if the switch is turned on."""
- return self._on_state
-
- @property
- def name(self):
- """Return the name of the entity."""
- return self._name
-
- @property
- def unique_id(self):
- """Return an unique identifier for this entity."""
- return self._unique_id
-
- @property
- def should_poll(self):
- """Return False because entity pushes its state to HA."""
- return False
+ """Return True if the entity is on."""
+ return self._state_dict[STATE_KEY_STATE]
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._ads_hub.write_by_name(
- self.ads_var, True, self._ads_hub.PLCTYPE_BOOL)
+ self._ads_var, True, self._ads_hub.PLCTYPE_BOOL)
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._ads_hub.write_by_name(
- self.ads_var, False, self._ads_hub.PLCTYPE_BOOL)
+ self._ads_var, False, self._ads_hub.PLCTYPE_BOOL)
diff --git a/homeassistant/components/aftership/const.py b/homeassistant/components/aftership/const.py
new file mode 100644
index 00000000000..e096aa14911
--- /dev/null
+++ b/homeassistant/components/aftership/const.py
@@ -0,0 +1,2 @@
+"""Constants for the Aftership integration."""
+DOMAIN = 'aftership'
diff --git a/homeassistant/components/aftership/manifest.json b/homeassistant/components/aftership/manifest.json
new file mode 100644
index 00000000000..b9ee8939dc4
--- /dev/null
+++ b/homeassistant/components/aftership/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "aftership",
+ "name": "Aftership",
+ "documentation": "https://www.home-assistant.io/components/aftership",
+ "requirements": [
+ "pyaftership==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py
index eb5188a95cb..fae3e38c96b 100644
--- a/homeassistant/components/aftership/sensor.py
+++ b/homeassistant/components/aftership/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for non-delivered packages recorded in AfterShip.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/sensor.aftership/
-"""
+"""Support for non-delivered packages recorded in AfterShip."""
from datetime import timedelta
import logging
@@ -13,24 +8,44 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-
-REQUIREMENTS = ['pyaftership==0.1.2']
+from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Information provided by AfterShip'
+ATTR_TRACKINGS = 'trackings'
+
+BASE = 'https://track.aftership.com/'
CONF_SLUG = 'slug'
CONF_TITLE = 'title'
CONF_TRACKING_NUMBER = 'tracking_number'
DEFAULT_NAME = 'aftership'
+UPDATE_TOPIC = DOMAIN + '_update'
ICON = 'mdi:package-variant-closed'
-MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30)
+MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
+
+SERVICE_ADD_TRACKING = 'add_tracking'
+SERVICE_REMOVE_TRACKING = 'remove_tracking'
+
+ADD_TRACKING_SERVICE_SCHEMA = vol.Schema(
+ {
+ vol.Required(CONF_TRACKING_NUMBER): cv.string,
+ vol.Optional(CONF_TITLE): cv.string,
+ vol.Optional(CONF_SLUG): cv.string,
+ }
+)
+
+REMOVE_TRACKING_SERVICE_SCHEMA = vol.Schema(
+ {vol.Required(CONF_SLUG): cv.string,
+ vol.Required(CONF_TRACKING_NUMBER): cv.string}
+)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
@@ -56,7 +71,40 @@ async def async_setup_platform(
aftership.meta)
return
- async_add_entities([AfterShipSensor(aftership, name)], True)
+ instance = AfterShipSensor(aftership, name)
+
+ async_add_entities([instance], True)
+
+ async def handle_add_tracking(call):
+ """Call when a user adds a new Aftership tracking from HASS."""
+ title = call.data.get(CONF_TITLE)
+ slug = call.data.get(CONF_SLUG)
+ tracking_number = call.data[CONF_TRACKING_NUMBER]
+
+ await aftership.add_package_tracking(tracking_number, title, slug)
+ async_dispatcher_send(hass, UPDATE_TOPIC)
+
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_ADD_TRACKING,
+ handle_add_tracking,
+ schema=ADD_TRACKING_SERVICE_SCHEMA,
+ )
+
+ async def handle_remove_tracking(call):
+ """Call when a user removes an Aftership tracking from HASS."""
+ slug = call.data[CONF_SLUG]
+ tracking_number = call.data[CONF_TRACKING_NUMBER]
+
+ await aftership.remove_package_tracking(slug, tracking_number)
+ async_dispatcher_send(hass, UPDATE_TOPIC)
+
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_REMOVE_TRACKING,
+ handle_remove_tracking,
+ schema=REMOVE_TRACKING_SERVICE_SCHEMA,
+ )
class AfterShipSensor(Entity):
@@ -94,8 +142,18 @@ class AfterShipSensor(Entity):
"""Icon to use in the frontend."""
return ICON
+ async def async_added_to_hass(self):
+ """Register callbacks."""
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ UPDATE_TOPIC, self.force_update)
+
+ async def force_update(self):
+ """Force update of data."""
+ await self.async_update(no_throttle=True)
+ await self.async_update_ha_state()
+
@Throttle(MIN_TIME_BETWEEN_UPDATES)
- async def async_update(self):
+ async def async_update(self, **kwargs):
"""Get the latest data from the AfterShip API."""
await self.aftership.get_trackings()
@@ -109,12 +167,29 @@ class AfterShipSensor(Entity):
status_to_ignore = {'delivered'}
status_counts = {}
+ trackings = []
not_delivered_count = 0
- for tracking in self.aftership.trackings['trackings']:
- status = tracking['tag'].lower()
- name = tracking['tracking_number']
- status_counts[status] = status_counts.get(status, 0)+1
+ for track in self.aftership.trackings['trackings']:
+ status = track['tag'].lower()
+ name = (
+ track['tracking_number']
+ if track['title'] is None
+ else track['title']
+ )
+ status_counts[status] = status_counts.get(status, 0) + 1
+ trackings.append({
+ 'name': name,
+ 'tracking_number': track['tracking_number'],
+ 'slug': track['slug'],
+ 'link': '%s%s/%s' %
+ (BASE, track['slug'], track['tracking_number']),
+ 'last_update': track['updated_at'],
+ 'expected_delivery': track['expected_delivery'],
+ 'status': track['tag'],
+ 'last_checkpoint': track['checkpoints'][-1]
+ })
+
if status not in status_to_ignore:
not_delivered_count += 1
else:
@@ -122,7 +197,8 @@ class AfterShipSensor(Entity):
self._attributes = {
ATTR_ATTRIBUTION: ATTRIBUTION,
- **status_counts
+ **status_counts,
+ ATTR_TRACKINGS: trackings,
}
self._state = not_delivered_count
diff --git a/homeassistant/components/aftership/services.yaml b/homeassistant/components/aftership/services.yaml
new file mode 100644
index 00000000000..157156c3252
--- /dev/null
+++ b/homeassistant/components/aftership/services.yaml
@@ -0,0 +1,24 @@
+# Describes the format for available aftership services
+
+add_tracking:
+ description: Add new tracking to Aftership.
+ fields:
+ tracking_number:
+ description: Tracking number for the new tracking
+ example: '123456789'
+ title:
+ description: A custom title for the new tracking
+ example: 'Laptop'
+ slug:
+ description: Slug (carrier) of the new tracking
+ example: 'USPS'
+
+remove_tracking:
+ description: Remove a tracking from Aftership.
+ fields:
+ tracking_number:
+ description: Tracking number of the tracking to remove
+ example: '123456789'
+ slug:
+ description: Slug (carrier) of the tracking to remove
+ example: 'USPS'
diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py
index 66af51efcb1..87d5c3b6bd4 100644
--- a/homeassistant/components/air_quality/__init__.py
+++ b/homeassistant/components/air_quality/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component for handling Air Quality data for your location.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/air_quality/
-"""
+"""Component for handling Air Quality data for your location."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/air_quality/manifest.json b/homeassistant/components/air_quality/manifest.json
new file mode 100644
index 00000000000..5bfe85547ff
--- /dev/null
+++ b/homeassistant/components/air_quality/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "air_quality",
+ "name": "Air quality",
+ "documentation": "https://www.home-assistant.io/components/air_quality",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json
new file mode 100644
index 00000000000..ddb109a99b0
--- /dev/null
+++ b/homeassistant/components/airvisual/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "airvisual",
+ "name": "Airvisual",
+ "documentation": "https://www.home-assistant.io/components/airvisual",
+ "requirements": [
+ "pyairvisual==3.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py
index b9e7a3315e3..55670020133 100644
--- a/homeassistant/components/airvisual/sensor.py
+++ b/homeassistant/components/airvisual/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for AirVisual air quality sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.airvisual/
-"""
+"""Support for AirVisual air quality sensors."""
from logging import getLogger
from datetime import timedelta
@@ -18,7 +13,6 @@ from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyairvisual==3.0.1']
_LOGGER = getLogger(__name__)
ATTR_CITY = 'city'
diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py
index 4627ba77781..70c85fd58a5 100644
--- a/homeassistant/components/aladdin_connect/cover.py
+++ b/homeassistant/components/aladdin_connect/cover.py
@@ -1,9 +1,4 @@
-"""
-Platform for the Aladdin Connect cover component.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/cover.aladdin_connect/
-"""
+"""Platform for the Aladdin Connect cover component."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED,
STATE_OPENING, STATE_CLOSING, STATE_OPEN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['aladdin_connect==0.3']
-
_LOGGER = logging.getLogger(__name__)
NOTIFICATION_ID = 'aladdin_notification'
diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json
new file mode 100644
index 00000000000..0681d5df38b
--- /dev/null
+++ b/homeassistant/components/aladdin_connect/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "aladdin_connect",
+ "name": "Aladdin connect",
+ "documentation": "https://www.home-assistant.io/components/aladdin_connect",
+ "requirements": [
+ "aladdin_connect==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py
index 86bb3e73bda..36a68eda174 100644
--- a/homeassistant/components/alarm_control_panel/__init__.py
+++ b/homeassistant/components/alarm_control_panel/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to interface with an alarm control panel.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel/
-"""
+"""Component to interface with an alarm control panel."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/alarm_control_panel/manifest.json b/homeassistant/components/alarm_control_panel/manifest.json
new file mode 100644
index 00000000000..95e26de53bc
--- /dev/null
+++ b/homeassistant/components/alarm_control_panel/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "alarm_control_panel",
+ "name": "Alarm control panel",
+ "documentation": "https://www.home-assistant.io/components/alarm_control_panel",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@colinodell"
+ ]
+}
diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py
index 5b1296b39de..b4d1a2e0b9f 100644
--- a/homeassistant/components/alarmdecoder/__init__.py
+++ b/homeassistant/components/alarmdecoder/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers.discovery import load_platform
from homeassistant.util import dt as dt_util
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
-REQUIREMENTS = ['alarmdecoder==1.13.2']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'alarmdecoder'
diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py
index d7eced933dd..51645b516b9 100644
--- a/homeassistant/components/alarmdecoder/alarm_control_panel.py
+++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py
@@ -13,8 +13,6 @@ from . import DATA_AD, SIGNAL_PANEL_MESSAGE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['alarmdecoder']
-
SERVICE_ALARM_TOGGLE_CHIME = 'alarmdecoder_alarm_toggle_chime'
ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({
vol.Required(ATTR_CODE): cv.string,
diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py
index 09e63b4d664..91ff8b381b5 100644
--- a/homeassistant/components/alarmdecoder/binary_sensor.py
+++ b/homeassistant/components/alarmdecoder/binary_sensor.py
@@ -8,8 +8,6 @@ from . import (
CONF_ZONE_RFID, CONF_ZONE_TYPE, CONF_ZONES, SIGNAL_REL_MESSAGE,
SIGNAL_RFX_MESSAGE, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, ZONE_SCHEMA)
-DEPENDENCIES = ['alarmdecoder']
-
_LOGGER = logging.getLogger(__name__)
ATTR_RF_BIT0 = 'rf_bit0'
diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json
new file mode 100644
index 00000000000..3e0d4112d27
--- /dev/null
+++ b/homeassistant/components/alarmdecoder/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "alarmdecoder",
+ "name": "Alarmdecoder",
+ "documentation": "https://www.home-assistant.io/components/alarmdecoder",
+ "requirements": [
+ "alarmdecoder==1.13.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py
index 88371dad17a..9fb37d62376 100644
--- a/homeassistant/components/alarmdecoder/sensor.py
+++ b/homeassistant/components/alarmdecoder/sensor.py
@@ -7,8 +7,6 @@ from . import SIGNAL_PANEL_MESSAGE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['alarmdecoder']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up for AlarmDecoder sensor devices."""
diff --git a/homeassistant/components/alarmdecoder/services.yaml b/homeassistant/components/alarmdecoder/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/alarmdotcom/alarm_control_panel.py b/homeassistant/components/alarmdotcom/alarm_control_panel.py
index 4f2913771b1..5919bf84f41 100644
--- a/homeassistant/components/alarmdotcom/alarm_control_panel.py
+++ b/homeassistant/components/alarmdotcom/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Interfaces with Alarm.com alarm control panels.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
-"""
+"""Interfaces with Alarm.com alarm control panels."""
import logging
import re
@@ -17,8 +12,6 @@ from homeassistant.const import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyalarmdotcom==0.3.2']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Alarm.com'
diff --git a/homeassistant/components/alarmdotcom/manifest.json b/homeassistant/components/alarmdotcom/manifest.json
new file mode 100644
index 00000000000..9d2c0a2056e
--- /dev/null
+++ b/homeassistant/components/alarmdotcom/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "alarmdotcom",
+ "name": "Alarmdotcom",
+ "documentation": "https://www.home-assistant.io/components/alarmdotcom",
+ "requirements": [
+ "pyalarmdotcom==0.3.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/alert/manifest.json b/homeassistant/components/alert/manifest.json
new file mode 100644
index 00000000000..f3dcc18208c
--- /dev/null
+++ b/homeassistant/components/alert/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "alert",
+ "name": "Alert",
+ "documentation": "https://www.home-assistant.io/components/alert",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py
index 062d698d512..862605b64b5 100644
--- a/homeassistant/components/alexa/__init__.py
+++ b/homeassistant/components/alexa/__init__.py
@@ -17,8 +17,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_FLASH_BRIEFINGS = 'flash_briefings'
CONF_SMART_HOME = 'smart_home'
-DEPENDENCIES = ['http']
-
ALEXA_ENTITY_SCHEMA = vol.Schema({
vol.Optional(smart_home.CONF_DESCRIPTION): cv.string,
vol.Optional(smart_home.CONF_DISPLAY_CATEGORIES): cv.string,
diff --git a/homeassistant/components/alexa/manifest.json b/homeassistant/components/alexa/manifest.json
new file mode 100644
index 00000000000..e4fc9eb8680
--- /dev/null
+++ b/homeassistant/components/alexa/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "alexa",
+ "name": "Alexa",
+ "documentation": "https://www.home-assistant.io/components/alexa",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/alexa/services.yaml b/homeassistant/components/alexa/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/alpha_vantage/manifest.json b/homeassistant/components/alpha_vantage/manifest.json
new file mode 100644
index 00000000000..dacc428ea2e
--- /dev/null
+++ b/homeassistant/components/alpha_vantage/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "alpha_vantage",
+ "name": "Alpha vantage",
+ "documentation": "https://www.home-assistant.io/components/alpha_vantage",
+ "requirements": [
+ "alpha_vantage==2.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py
index 774a3fe95f6..9ea6797a56e 100644
--- a/homeassistant/components/alpha_vantage/sensor.py
+++ b/homeassistant/components/alpha_vantage/sensor.py
@@ -1,9 +1,4 @@
-"""
-Stock market information from Alpha Vantage.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.alpha_vantage/
-"""
+"""Stock market information from Alpha Vantage."""
from datetime import timedelta
import logging
@@ -15,8 +10,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['alpha_vantage==2.1.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CLOSE = 'close'
diff --git a/homeassistant/components/amazon_polly/manifest.json b/homeassistant/components/amazon_polly/manifest.json
new file mode 100644
index 00000000000..19140aac939
--- /dev/null
+++ b/homeassistant/components/amazon_polly/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "amazon_polly",
+ "name": "Amazon polly",
+ "documentation": "https://www.home-assistant.io/components/amazon_polly",
+ "requirements": [
+ "boto3==1.9.16"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/amazon_polly/tts.py b/homeassistant/components/amazon_polly/tts.py
index 167cd9cfc78..4511a587a60 100644
--- a/homeassistant/components/amazon_polly/tts.py
+++ b/homeassistant/components/amazon_polly/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the Amazon Polly text to speech service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts.amazon_polly/
-"""
+"""Support for the Amazon Polly text to speech service."""
import logging
import voluptuous as vol
@@ -11,8 +6,6 @@ import voluptuous as vol
from homeassistant.components.tts import PLATFORM_SCHEMA, Provider
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['boto3==1.9.16']
-
_LOGGER = logging.getLogger(__name__)
CONF_REGION = 'region_name'
diff --git a/homeassistant/components/ambient_station/.translations/es.json b/homeassistant/components/ambient_station/.translations/es.json
index d6732423a7e..d4b0075aa65 100644
--- a/homeassistant/components/ambient_station/.translations/es.json
+++ b/homeassistant/components/ambient_station/.translations/es.json
@@ -1,9 +1,19 @@
{
"config": {
+ "error": {
+ "identifier_exists": "La clave API y/o la clave de aplicaci\u00f3n ya est\u00e1 registrada",
+ "invalid_key": "Clave API y/o clave de aplicaci\u00f3n no v\u00e1lida",
+ "no_devices": "No se han encontrado dispositivos en la cuenta"
+ },
"step": {
"user": {
+ "data": {
+ "api_key": "Clave API",
+ "app_key": "Clave de aplicaci\u00f3n"
+ },
"title": "Completa tu informaci\u00f3n"
}
- }
+ },
+ "title": "Ambient PWS"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/ambient_station/.translations/fr.json b/homeassistant/components/ambient_station/.translations/fr.json
index ede25d0bd4b..b28cb374eac 100644
--- a/homeassistant/components/ambient_station/.translations/fr.json
+++ b/homeassistant/components/ambient_station/.translations/fr.json
@@ -13,6 +13,7 @@
},
"title": "Veuillez saisir vos informations"
}
- }
+ },
+ "title": "Ambient PWS"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/ambient_station/.translations/ko.json b/homeassistant/components/ambient_station/.translations/ko.json
index c316741d36b..541b8699dc8 100644
--- a/homeassistant/components/ambient_station/.translations/ko.json
+++ b/homeassistant/components/ambient_station/.translations/ko.json
@@ -11,7 +11,7 @@
"api_key": "API \ud0a4",
"app_key": "Application \ud0a4"
},
- "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694"
+ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825"
}
},
"title": "Ambient PWS"
diff --git a/homeassistant/components/ambient_station/.translations/th.json b/homeassistant/components/ambient_station/.translations/th.json
index 9f08ed5f1a2..a6115413edc 100644
--- a/homeassistant/components/ambient_station/.translations/th.json
+++ b/homeassistant/components/ambient_station/.translations/th.json
@@ -1,5 +1,8 @@
{
"config": {
+ "error": {
+ "no_devices": "\u0e44\u0e21\u0e48\u0e1e\u0e1a\u0e2d\u0e38\u0e1b\u0e01\u0e23\u0e13\u0e4c\u0e43\u0e14\u0e46 \u0e43\u0e19\u0e1a\u0e31\u0e0d\u0e0a\u0e35\u0e40\u0e25\u0e22"
+ },
"step": {
"user": {
"data": {
diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py
index cb0a2067d9f..2c185c3bc71 100644
--- a/homeassistant/components/ambient_station/__init__.py
+++ b/homeassistant/components/ambient_station/__init__.py
@@ -20,8 +20,6 @@ from .const import (
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE,
TYPE_BINARY_SENSOR, TYPE_SENSOR)
-REQUIREMENTS = ['aioambient==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
DATA_CONFIG = 'config'
@@ -329,6 +327,8 @@ class AmbientStation:
"""Define a handler to fire when the websocket is connected."""
_LOGGER.info('Connected to websocket')
_LOGGER.debug('Watchdog starting')
+ if self._watchdog_listener:
+ self._watchdog_listener()
self._watchdog_listener = async_call_later(
self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py
index 04a38901683..02f7590c307 100644
--- a/homeassistant/components/ambient_station/binary_sensor.py
+++ b/homeassistant/components/ambient_station/binary_sensor.py
@@ -12,8 +12,6 @@ from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ambient_station']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/ambient_station/manifest.json b/homeassistant/components/ambient_station/manifest.json
new file mode 100644
index 00000000000..11d2ad3668e
--- /dev/null
+++ b/homeassistant/components/ambient_station/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ambient_station",
+ "name": "Ambient station",
+ "documentation": "https://www.home-assistant.io/components/ambient_station",
+ "requirements": [
+ "aioambient==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py
index b394dc558e6..9c50d97fb03 100644
--- a/homeassistant/components/ambient_station/sensor.py
+++ b/homeassistant/components/ambient_station/sensor.py
@@ -8,8 +8,6 @@ from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ambient_station']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py
index 9f43941505b..3a0a983fceb 100644
--- a/homeassistant/components/amcrest/__init__.py
+++ b/homeassistant/components/amcrest/__init__.py
@@ -7,14 +7,11 @@ import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
- CONF_SENSORS, CONF_SWITCHES, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION)
+ CONF_BINARY_SENSORS, CONF_SENSORS, CONF_SWITCHES, CONF_SCAN_INTERVAL,
+ HTTP_BASIC_AUTHENTICATION)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['amcrest==1.2.7']
-DEPENDENCIES = ['ffmpeg']
-
_LOGGER = logging.getLogger(__name__)
CONF_AUTHENTICATION = 'authentication'
@@ -52,9 +49,14 @@ STREAM_SOURCE_LIST = {
'rtsp': 2,
}
+BINARY_SENSORS = {
+ 'motion_detected': 'Motion Detected'
+}
+
# Sensor types are defined like: Name, units, icon
+SENSOR_MOTION_DETECTOR = 'motion_detector'
SENSORS = {
- 'motion_detector': ['Motion Detected', None, 'mdi:run'],
+ SENSOR_MOTION_DETECTOR: ['Motion Detected', None, 'mdi:run'],
'sdcard': ['SD Used', '%', 'mdi:sd'],
'ptz_preset': ['PTZ Preset', None, 'mdi:camera-iris'],
}
@@ -65,28 +67,49 @@ SWITCHES = {
'motion_recording': ['Motion Recording', 'mdi:record-rec']
}
+
+def _deprecated_sensors(value):
+ if SENSOR_MOTION_DETECTOR in value:
+ _LOGGER.warning(
+ 'sensors option %s is deprecated. '
+ 'Please remove from your configuration and '
+ 'use binary_sensors option motion_detected instead.',
+ SENSOR_MOTION_DETECTOR)
+ return value
+
+
+def _has_unique_names(value):
+ names = [camera[CONF_NAME] for camera in value]
+ vol.Schema(vol.Unique())(names)
+ return value
+
+
+AMCREST_SCHEMA = vol.Schema({
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
+ vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
+ vol.All(vol.In(AUTHENTICATION_LIST)),
+ vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
+ vol.All(vol.In(RESOLUTION_LIST)),
+ vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
+ vol.All(vol.In(STREAM_SOURCE_LIST)),
+ vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS):
+ cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
+ cv.time_period,
+ vol.Optional(CONF_BINARY_SENSORS):
+ vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]),
+ vol.Optional(CONF_SENSORS):
+ vol.All(cv.ensure_list, [vol.In(SENSORS)], _deprecated_sensors),
+ vol.Optional(CONF_SWITCHES):
+ vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
+})
+
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
- vol.Required(CONF_HOST): cv.string,
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
- vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
- vol.All(vol.In(AUTHENTICATION_LIST)),
- vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
- vol.All(vol.In(RESOLUTION_LIST)),
- vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
- vol.All(vol.In(STREAM_SOURCE_LIST)),
- vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS):
- cv.string,
- vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
- cv.time_period,
- vol.Optional(CONF_SENSORS):
- vol.All(cv.ensure_list, [vol.In(SENSORS)]),
- vol.Optional(CONF_SWITCHES):
- vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
- })])
+ DOMAIN: vol.All(cv.ensure_list, [AMCREST_SCHEMA], _has_unique_names)
}, extra=vol.ALLOW_EXTRA)
@@ -94,20 +117,24 @@ def setup(hass, config):
"""Set up the Amcrest IP Camera component."""
from amcrest import AmcrestCamera, AmcrestError
- hass.data[DATA_AMCREST] = {}
+ hass.data.setdefault(DATA_AMCREST, {})
amcrest_cams = config[DOMAIN]
for device in amcrest_cams:
+ name = device[CONF_NAME]
+ username = device[CONF_USERNAME]
+ password = device[CONF_PASSWORD]
+
try:
- camera = AmcrestCamera(device.get(CONF_HOST),
- device.get(CONF_PORT),
- device.get(CONF_USERNAME),
- device.get(CONF_PASSWORD)).camera
+ camera = AmcrestCamera(device[CONF_HOST],
+ device[CONF_PORT],
+ username,
+ password).camera
# pylint: disable=pointless-statement
camera.current_time
except AmcrestError as ex:
- _LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex))
+ _LOGGER.error("Unable to connect to %s camera: %s", name, str(ex))
hass.components.persistent_notification.create(
'Error: {}
'
'You will need to restart hass after fixing.'
@@ -116,23 +143,19 @@ def setup(hass, config):
notification_id=NOTIFICATION_ID)
continue
- ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS)
- name = device.get(CONF_NAME)
- resolution = RESOLUTION_LIST[device.get(CONF_RESOLUTION)]
+ ffmpeg_arguments = device[CONF_FFMPEG_ARGUMENTS]
+ resolution = RESOLUTION_LIST[device[CONF_RESOLUTION]]
+ binary_sensors = device.get(CONF_BINARY_SENSORS)
sensors = device.get(CONF_SENSORS)
switches = device.get(CONF_SWITCHES)
- stream_source = STREAM_SOURCE_LIST[device.get(CONF_STREAM_SOURCE)]
-
- username = device.get(CONF_USERNAME)
- password = device.get(CONF_PASSWORD)
+ stream_source = STREAM_SOURCE_LIST[device[CONF_STREAM_SOURCE]]
# currently aiohttp only works with basic authentication
# only valid for mjpeg streaming
- if username is not None and password is not None:
- if device.get(CONF_AUTHENTICATION) == HTTP_BASIC_AUTHENTICATION:
- authentication = aiohttp.BasicAuth(username, password)
- else:
- authentication = None
+ if device[CONF_AUTHENTICATION] == HTTP_BASIC_AUTHENTICATION:
+ authentication = aiohttp.BasicAuth(username, password)
+ else:
+ authentication = None
hass.data[DATA_AMCREST][name] = AmcrestDevice(
camera, name, authentication, ffmpeg_arguments, stream_source,
@@ -143,6 +166,13 @@ def setup(hass, config):
CONF_NAME: name,
}, config)
+ if binary_sensors:
+ discovery.load_platform(
+ hass, 'binary_sensor', DOMAIN, {
+ CONF_NAME: name,
+ CONF_BINARY_SENSORS: binary_sensors
+ }, config)
+
if sensors:
discovery.load_platform(
hass, 'sensor', DOMAIN, {
@@ -157,7 +187,7 @@ def setup(hass, config):
CONF_SWITCHES: switches
}, config)
- return True
+ return len(hass.data[DATA_AMCREST]) >= 1
class AmcrestDevice:
diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py
new file mode 100644
index 00000000000..b591616a88d
--- /dev/null
+++ b/homeassistant/components/amcrest/binary_sensor.py
@@ -0,0 +1,69 @@
+"""Suppoort for Amcrest IP camera binary sensors."""
+from datetime import timedelta
+import logging
+
+from homeassistant.components.binary_sensor import (
+ BinarySensorDevice, DEVICE_CLASS_MOTION)
+from homeassistant.const import CONF_NAME, CONF_BINARY_SENSORS
+from . import DATA_AMCREST, BINARY_SENSORS
+
+_LOGGER = logging.getLogger(__name__)
+
+SCAN_INTERVAL = timedelta(seconds=5)
+
+
+async def async_setup_platform(hass, config, async_add_devices,
+ discovery_info=None):
+ """Set up a binary sensor for an Amcrest IP Camera."""
+ if discovery_info is None:
+ return
+
+ device_name = discovery_info[CONF_NAME]
+ binary_sensors = discovery_info[CONF_BINARY_SENSORS]
+ amcrest = hass.data[DATA_AMCREST][device_name]
+
+ amcrest_binary_sensors = []
+ for sensor_type in binary_sensors:
+ amcrest_binary_sensors.append(
+ AmcrestBinarySensor(amcrest.name, amcrest.device, sensor_type))
+
+ async_add_devices(amcrest_binary_sensors, True)
+
+
+class AmcrestBinarySensor(BinarySensorDevice):
+ """Binary sensor for Amcrest camera."""
+
+ def __init__(self, name, camera, sensor_type):
+ """Initialize entity."""
+ self._name = '{} {}'.format(name, BINARY_SENSORS[sensor_type])
+ self._camera = camera
+ self._sensor_type = sensor_type
+ self._state = None
+
+ @property
+ def name(self):
+ """Return entity name."""
+ return self._name
+
+ @property
+ def is_on(self):
+ """Return if entity is on."""
+ return self._state
+
+ @property
+ def device_class(self):
+ """Return device class."""
+ return DEVICE_CLASS_MOTION
+
+ def update(self):
+ """Update entity."""
+ from amcrest import AmcrestError
+
+ _LOGGER.debug('Pulling data from %s binary sensor', self._name)
+
+ try:
+ self._state = self._camera.is_motion_detected
+ except AmcrestError as error:
+ _LOGGER.error(
+ 'Could not update %s binary sensor due to error: %s',
+ self.name, error)
diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py
index 63c2c720781..07f5d403ba8 100644
--- a/homeassistant/components/amcrest/camera.py
+++ b/homeassistant/components/amcrest/camera.py
@@ -3,7 +3,7 @@ import asyncio
import logging
from homeassistant.components.camera import (
- Camera, SUPPORT_STREAM)
+ Camera, SUPPORT_ON_OFF, SUPPORT_STREAM)
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import CONF_NAME
from homeassistant.helpers.aiohttp_client import (
@@ -12,8 +12,6 @@ from homeassistant.helpers.aiohttp_client import (
from . import DATA_AMCREST, STREAM_SOURCE_LIST, TIMEOUT
-DEPENDENCIES = ['amcrest', 'ffmpeg']
-
_LOGGER = logging.getLogger(__name__)
@@ -28,8 +26,6 @@ async def async_setup_platform(hass, config, async_add_entities,
async_add_entities([AmcrestCam(hass, amcrest)], True)
- return True
-
class AmcrestCam(Camera):
"""An implementation of an Amcrest IP camera."""
@@ -39,18 +35,23 @@ class AmcrestCam(Camera):
super(AmcrestCam, self).__init__()
self._name = amcrest.name
self._camera = amcrest.device
- self._base_url = self._camera.get_base_url()
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = amcrest.ffmpeg_arguments
self._stream_source = amcrest.stream_source
self._resolution = amcrest.resolution
self._token = self._auth = amcrest.authentication
+ self._is_recording = False
+ self._model = None
self._snapshot_lock = asyncio.Lock()
async def async_camera_image(self):
"""Return a still image response from the camera."""
from amcrest import AmcrestError
+ if not self.is_on:
+ _LOGGER.error(
+ 'Attempt to take snaphot when %s camera is off', self.name)
+ return None
async with self._snapshot_lock:
try:
# Send the request to snap a picture and return raw jpg data
@@ -59,7 +60,8 @@ class AmcrestCam(Camera):
return response.data
except AmcrestError as error:
_LOGGER.error(
- 'Could not get camera image due to error %s', error)
+ 'Could not get image from %s camera due to error: %s',
+ self.name, error)
return None
async def handle_async_mjpeg_stream(self, request):
@@ -94,6 +96,8 @@ class AmcrestCam(Camera):
finally:
await stream.close()
+ # Entity property overrides
+
@property
def name(self):
"""Return the name of this camera."""
@@ -102,9 +106,80 @@ class AmcrestCam(Camera):
@property
def supported_features(self):
"""Return supported features."""
- return SUPPORT_STREAM
+ return SUPPORT_ON_OFF | SUPPORT_STREAM
+
+ # Camera property overrides
+
+ @property
+ def is_recording(self):
+ """Return true if the device is recording."""
+ return self._is_recording
+
+ @property
+ def brand(self):
+ """Return the camera brand."""
+ return 'Amcrest'
+
+ @property
+ def model(self):
+ """Return the camera model."""
+ return self._model
@property
def stream_source(self):
"""Return the source of the stream."""
return self._camera.rtsp_url(typeno=self._resolution)
+
+ @property
+ def is_on(self):
+ """Return true if on."""
+ return self.is_streaming
+
+ # Other Entity method overrides
+
+ def update(self):
+ """Update entity status."""
+ from amcrest import AmcrestError
+
+ _LOGGER.debug('Pulling data from %s camera', self.name)
+ if self._model is None:
+ try:
+ self._model = self._camera.device_type.split('=')[-1].strip()
+ except AmcrestError as error:
+ _LOGGER.error(
+ 'Could not get %s camera model due to error: %s',
+ self.name, error)
+ self._model = ''
+ try:
+ self.is_streaming = self._camera.video_enabled
+ self._is_recording = self._camera.record_mode == 'Manual'
+ except AmcrestError as error:
+ _LOGGER.error(
+ 'Could not get %s camera attributes due to error: %s',
+ self.name, error)
+
+ # Other Camera method overrides
+
+ def turn_off(self):
+ """Turn off camera."""
+ self._enable_video_stream(False)
+
+ def turn_on(self):
+ """Turn on camera."""
+ self._enable_video_stream(True)
+
+ # Utility methods
+
+ def _enable_video_stream(self, enable):
+ """Enable or disable camera video stream."""
+ from amcrest import AmcrestError
+
+ try:
+ self._camera.video_enabled = enable
+ except AmcrestError as error:
+ _LOGGER.error(
+ 'Could not %s %s camera video stream due to error: %s',
+ 'enable' if enable else 'disable', self.name, error)
+ else:
+ self.is_streaming = enable
+ self.schedule_update_ha_state()
diff --git a/homeassistant/components/amcrest/manifest.json b/homeassistant/components/amcrest/manifest.json
new file mode 100644
index 00000000000..e05fdcf4bd4
--- /dev/null
+++ b/homeassistant/components/amcrest/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "amcrest",
+ "name": "Amcrest",
+ "documentation": "https://www.home-assistant.io/components/amcrest",
+ "requirements": [
+ "amcrest==1.3.0"
+ ],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py
index c721914c73c..56cb021052e 100644
--- a/homeassistant/components/amcrest/sensor.py
+++ b/homeassistant/components/amcrest/sensor.py
@@ -7,8 +7,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_AMCREST, SENSORS
-DEPENDENCIES = ['amcrest']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
@@ -30,7 +28,6 @@ async def async_setup_platform(
AmcrestSensor(amcrest.name, amcrest.device, sensor_type))
async_add_entities(amcrest_sensors, True)
- return True
class AmcrestSensor(Entity):
@@ -75,19 +72,6 @@ class AmcrestSensor(Entity):
"""Get the latest data and updates the state."""
_LOGGER.debug("Pulling data from %s sensor.", self._name)
- try:
- version, build_date = self._camera.software_information
- self._attrs['Build Date'] = build_date.split('=')[-1]
- self._attrs['Version'] = version.split('=')[-1]
- except ValueError:
- self._attrs['Build Date'] = 'Not Available'
- self._attrs['Version'] = 'Not Available'
-
- try:
- self._attrs['Serial Number'] = self._camera.serial_number
- except ValueError:
- self._attrs['Serial Number'] = 'Not Available'
-
if self._sensor_type == 'motion_detector':
self._state = self._camera.is_motion_detected
self._attrs['Record Mode'] = self._camera.record_mode
diff --git a/homeassistant/components/amcrest/switch.py b/homeassistant/components/amcrest/switch.py
index 0bbd290b3ac..90f750d1797 100644
--- a/homeassistant/components/amcrest/switch.py
+++ b/homeassistant/components/amcrest/switch.py
@@ -8,8 +8,6 @@ from . import DATA_AMCREST, SWITCHES
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['amcrest']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/ampio/__init__.py b/homeassistant/components/ampio/__init__.py
new file mode 100644
index 00000000000..5f7bb4a44fa
--- /dev/null
+++ b/homeassistant/components/ampio/__init__.py
@@ -0,0 +1 @@
+"""The Ampio component."""
diff --git a/homeassistant/components/ampio/air_quality.py b/homeassistant/components/ampio/air_quality.py
new file mode 100644
index 00000000000..339f490bae5
--- /dev/null
+++ b/homeassistant/components/ampio/air_quality.py
@@ -0,0 +1,95 @@
+"""Support for Ampio Air Quality data."""
+from datetime import timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.air_quality import (
+ PLATFORM_SCHEMA, AirQualityEntity)
+from homeassistant.const import CONF_NAME
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+import homeassistant.helpers.config_validation as cv
+from homeassistant.util import Throttle
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTRIBUTION = 'Data provided by Ampio'
+CONF_STATION_ID = 'station_id'
+SCAN_INTERVAL = timedelta(minutes=10)
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_STATION_ID): cv.string,
+ vol.Optional(CONF_NAME): cv.string,
+})
+
+
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
+ """Set up the Ampio Smog air quality platform."""
+ from asmog import AmpioSmog
+
+ name = config.get(CONF_NAME)
+ station_id = config[CONF_STATION_ID]
+
+ session = async_get_clientsession(hass)
+ api = AmpioSmogMapData(AmpioSmog(station_id, hass.loop, session))
+
+ await api.async_update()
+
+ if not api.api.data:
+ _LOGGER.error("Station %s is not available", station_id)
+ return
+
+ async_add_entities([AmpioSmogQuality(api, station_id, name)], True)
+
+
+class AmpioSmogQuality(AirQualityEntity):
+ """Implementation of an Ampio Smog air quality entity."""
+
+ def __init__(self, api, station_id, name):
+ """Initialize the air quality entity."""
+ self._ampio = api
+ self._station_id = station_id
+ self._name = name or api.api.name
+
+ @property
+ def name(self):
+ """Return the name of the air quality entity."""
+ return self._name
+
+ @property
+ def unique_id(self):
+ """Return unique_name."""
+ return "ampio_smog_{}".format(self._station_id)
+
+ @property
+ def particulate_matter_2_5(self):
+ """Return the particulate matter 2.5 level."""
+ return self._ampio.api.pm2_5
+
+ @property
+ def particulate_matter_10(self):
+ """Return the particulate matter 10 level."""
+ return self._ampio.api.pm10
+
+ @property
+ def attribution(self):
+ """Return the attribution."""
+ return ATTRIBUTION
+
+ async def async_update(self):
+ """Get the latest data from the AmpioMap API."""
+ await self._ampio.async_update()
+
+
+class AmpioSmogMapData:
+ """Get the latest data and update the states."""
+
+ def __init__(self, api):
+ """Initialize the data object."""
+ self.api = api
+
+ @Throttle(SCAN_INTERVAL)
+ async def async_update(self):
+ """Get the latest data from AmpioMap."""
+ await self.api.get_data()
diff --git a/homeassistant/components/ampio/manifest.json b/homeassistant/components/ampio/manifest.json
new file mode 100644
index 00000000000..d20b10b2d15
--- /dev/null
+++ b/homeassistant/components/ampio/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ampio",
+ "name": "Ampio",
+ "documentation": "https://www.home-assistant.io/components/ampio",
+ "requirements": [
+ "asmog==0.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py
index 600efd55a16..dfb6d143e9a 100644
--- a/homeassistant/components/android_ip_webcam/__init__.py
+++ b/homeassistant/components/android_ip_webcam/__init__.py
@@ -21,8 +21,6 @@ from homeassistant.util.dt import utcnow
from homeassistant.components.mjpeg.camera import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL)
-REQUIREMENTS = ['pydroid-ipcam==0.8']
-
_LOGGER = logging.getLogger(__name__)
ATTR_AUD_CONNS = 'Audio Connections'
diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py
index c058c44c503..dbe50d81862 100644
--- a/homeassistant/components/android_ip_webcam/binary_sensor.py
+++ b/homeassistant/components/android_ip_webcam/binary_sensor.py
@@ -3,8 +3,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity
-DEPENDENCIES = ['android_ip_webcam']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/android_ip_webcam/manifest.json b/homeassistant/components/android_ip_webcam/manifest.json
new file mode 100644
index 00000000000..28909f7e053
--- /dev/null
+++ b/homeassistant/components/android_ip_webcam/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "android_ip_webcam",
+ "name": "Android ip webcam",
+ "documentation": "https://www.home-assistant.io/components/android_ip_webcam",
+ "requirements": [
+ "pydroid-ipcam==0.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py
index 4d29493d64f..9748b6ba548 100644
--- a/homeassistant/components/android_ip_webcam/sensor.py
+++ b/homeassistant/components/android_ip_webcam/sensor.py
@@ -5,8 +5,6 @@ from . import (
CONF_HOST, CONF_NAME, CONF_SENSORS, DATA_IP_WEBCAM, ICON_MAP, KEY_MAP,
AndroidIPCamEntity)
-DEPENDENCIES = ['android_ip_webcam']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py
index 0304c5747f7..e894913f5a4 100644
--- a/homeassistant/components/android_ip_webcam/switch.py
+++ b/homeassistant/components/android_ip_webcam/switch.py
@@ -5,8 +5,6 @@ from . import (
CONF_HOST, CONF_NAME, CONF_SWITCHES, DATA_IP_WEBCAM, ICON_MAP, KEY_MAP,
AndroidIPCamEntity)
-DEPENDENCIES = ['android_ip_webcam']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/androidtv/__init__.py b/homeassistant/components/androidtv/__init__.py
index fd108e05973..14832aef315 100644
--- a/homeassistant/components/androidtv/__init__.py
+++ b/homeassistant/components/androidtv/__init__.py
@@ -1,6 +1 @@
-"""
-Support for functionality to interact with Android TV and Fire TV devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.androidtv/
-"""
+"""Support for functionality to interact with Android TV/Fire TV devices."""
diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json
new file mode 100644
index 00000000000..841ad299785
--- /dev/null
+++ b/homeassistant/components/androidtv/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "androidtv",
+ "name": "Androidtv",
+ "documentation": "https://www.home-assistant.io/components/androidtv",
+ "requirements": [
+ "androidtv==0.0.15"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py
index 5bce21f05a0..030f0425df0 100644
--- a/homeassistant/components/androidtv/media_player.py
+++ b/homeassistant/components/androidtv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for functionality to interact with Android TV and Fire TV devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.androidtv/
-"""
+"""Support for functionality to interact with Android TV / Fire TV devices."""
import functools
import logging
import voluptuous as vol
@@ -18,12 +13,11 @@ from homeassistant.const import (
ATTR_COMMAND, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME,
CONF_PORT, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
STATE_STANDBY)
+from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
ANDROIDTV_DOMAIN = 'androidtv'
-REQUIREMENTS = ['androidtv==0.0.14']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_ANDROIDTV = SUPPORT_PAUSE | SUPPORT_PLAY | \
@@ -125,7 +119,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_LOGGER.warning("Could not connect to %s at %s%s",
device_name, host, adb_log)
- return
+ raise PlatformNotReady
if host in hass.data[ANDROIDTV_DOMAIN]:
_LOGGER.warning("Platform already setup on %s, skipping", host)
@@ -298,12 +292,12 @@ class ADBDevice(MediaPlayerDevice):
@adb_decorator()
def media_previous_track(self):
"""Send previous track command (results in rewind)."""
- self.aftv.media_previous()
+ self.aftv.media_previous_track()
@adb_decorator()
def media_next_track(self):
"""Send next track command (results in fast-forward)."""
- self.aftv.media_next()
+ self.aftv.media_next_track()
@adb_decorator()
def adb_command(self, cmd):
@@ -328,11 +322,10 @@ class AndroidTVDevice(ADBDevice):
turn_off_command)
self._device = None
- self._muted = None
self._device_properties = self.aftv.device_properties
- self._unique_id = 'androidtv-{}-{}'.format(
- name, self._device_properties['serialno'])
- self._volume = None
+ self._is_volume_muted = None
+ self._unique_id = self._device_properties.get('serialno')
+ self._volume_level = None
@adb_decorator(override_available=True)
def update(self):
@@ -349,16 +342,16 @@ class AndroidTVDevice(ADBDevice):
if not self._available:
return
- # Get the `state`, `current_app`, and `running_apps`.
- state, self._current_app, self._device, self._muted, self._volume = \
- self.aftv.update()
+ # Get the updated state and attributes.
+ state, self._current_app, self._device, self._is_volume_muted, \
+ self._volume_level = self.aftv.update()
self._state = ANDROIDTV_STATES[state]
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
- return self._muted
+ return self._is_volume_muted
@property
def source(self):
@@ -378,7 +371,7 @@ class AndroidTVDevice(ADBDevice):
@property
def volume_level(self):
"""Return the volume level."""
- return self._volume
+ return self._volume_level
@adb_decorator()
def media_stop(self):
@@ -393,12 +386,12 @@ class AndroidTVDevice(ADBDevice):
@adb_decorator()
def volume_down(self):
"""Send volume down command."""
- self.aftv.volume_down()
+ self._volume_level = self.aftv.volume_down(self._volume_level)
@adb_decorator()
def volume_up(self):
"""Send volume up command."""
- self.aftv.volume_up()
+ self._volume_level = self.aftv.volume_up(self._volume_level)
class FireTVDevice(ADBDevice):
diff --git a/homeassistant/components/anel_pwrctrl/manifest.json b/homeassistant/components/anel_pwrctrl/manifest.json
new file mode 100644
index 00000000000..17802918cd2
--- /dev/null
+++ b/homeassistant/components/anel_pwrctrl/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "anel_pwrctrl",
+ "name": "Anel pwrctrl",
+ "documentation": "https://www.home-assistant.io/components/anel_pwrctrl",
+ "requirements": [
+ "anel_pwrctrl-homeassistant==0.0.1.dev2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/anel_pwrctrl/switch.py b/homeassistant/components/anel_pwrctrl/switch.py
index fadb3cd96ff..7552e35fe4b 100644
--- a/homeassistant/components/anel_pwrctrl/switch.py
+++ b/homeassistant/components/anel_pwrctrl/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for ANEL PwrCtrl switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.pwrctrl/
-"""
+"""Support for ANEL PwrCtrl switches."""
import logging
import socket
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.util import Throttle
-REQUIREMENTS = ['anel_pwrctrl-homeassistant==0.0.1.dev2']
-
_LOGGER = logging.getLogger(__name__)
CONF_PORT_RECV = 'port_recv'
diff --git a/homeassistant/components/anthemav/manifest.json b/homeassistant/components/anthemav/manifest.json
new file mode 100644
index 00000000000..9b2e3c697bb
--- /dev/null
+++ b/homeassistant/components/anthemav/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "anthemav",
+ "name": "Anthemav",
+ "documentation": "https://www.home-assistant.io/components/anthemav",
+ "requirements": [
+ "anthemav==1.1.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py
index 36bc5ae10e1..1a335fc2ce6 100644
--- a/homeassistant/components/anthemav/media_player.py
+++ b/homeassistant/components/anthemav/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Anthem Network Receivers and Processors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.anthemav/
-"""
+"""Support for Anthem Network Receivers and Processors."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from homeassistant.const import (
STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['anthemav==1.1.10']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'anthemav'
diff --git a/homeassistant/components/apcupsd/__init__.py b/homeassistant/components/apcupsd/__init__.py
index aab6f6dda01..d4649db0203 100644
--- a/homeassistant/components/apcupsd/__init__.py
+++ b/homeassistant/components/apcupsd/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['apcaccess==0.0.13']
-
_LOGGER = logging.getLogger(__name__)
CONF_TYPE = 'type'
diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py
index 445dab9b074..367b3c2b9b5 100644
--- a/homeassistant/components/apcupsd/binary_sensor.py
+++ b/homeassistant/components/apcupsd/binary_sensor.py
@@ -8,8 +8,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components import apcupsd
DEFAULT_NAME = 'UPS Online Status'
-DEPENDENCIES = [apcupsd.DOMAIN]
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
diff --git a/homeassistant/components/apcupsd/manifest.json b/homeassistant/components/apcupsd/manifest.json
new file mode 100644
index 00000000000..813176728f2
--- /dev/null
+++ b/homeassistant/components/apcupsd/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "apcupsd",
+ "name": "Apcupsd",
+ "documentation": "https://www.home-assistant.io/components/apcupsd",
+ "requirements": [
+ "apcaccess==0.0.13"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py
index 09f9b324bdd..ae1ad10223d 100644
--- a/homeassistant/components/apcupsd/sensor.py
+++ b/homeassistant/components/apcupsd/sensor.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = [apcupsd.DOMAIN]
-
SENSOR_PREFIX = 'UPS '
SENSOR_TYPES = {
'alarmdel': ['Alarm Delay', '', 'mdi:alarm'],
diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py
index beba17ee2ea..0e860854af4 100644
--- a/homeassistant/components/api/__init__.py
+++ b/homeassistant/components/api/__init__.py
@@ -33,8 +33,6 @@ ATTR_REQUIRES_API_PASSWORD = 'requires_api_password'
ATTR_VERSION = 'version'
DOMAIN = 'api'
-DEPENDENCIES = ['http']
-
STREAM_PING_PAYLOAD = 'ping'
STREAM_PING_INTERVAL = 50 # seconds
diff --git a/homeassistant/components/api/manifest.json b/homeassistant/components/api/manifest.json
new file mode 100644
index 00000000000..25d9a76036e
--- /dev/null
+++ b/homeassistant/components/api/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "api",
+ "name": "Home Assistant API",
+ "documentation": "https://www.home-assistant.io/components/api",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/api/services.yaml b/homeassistant/components/api/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/api_streams/__init__.py b/homeassistant/components/api_streams/__init__.py
deleted file mode 100644
index dba43061313..00000000000
--- a/homeassistant/components/api_streams/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""The api_streams component."""
diff --git a/homeassistant/components/apns/manifest.json b/homeassistant/components/apns/manifest.json
new file mode 100644
index 00000000000..9a310a096a5
--- /dev/null
+++ b/homeassistant/components/apns/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "apns",
+ "name": "Apns",
+ "documentation": "https://www.home-assistant.io/components/apns",
+ "requirements": [
+ "apns2==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py
index b2c6b63864f..365bdbcb4f5 100644
--- a/homeassistant/components/apns/notify.py
+++ b/homeassistant/components/apns/notify.py
@@ -1,9 +1,4 @@
-"""
-APNS Notification platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.apns/
-"""
+"""APNS Notification platform."""
import logging
import os
@@ -18,8 +13,6 @@ from homeassistant.helpers.event import track_state_change
from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, DOMAIN, PLATFORM_SCHEMA, BaseNotificationService)
-REQUIREMENTS = ['apns2==0.3.0']
-
APNS_DEVICES = 'apns.yaml'
CONF_CERTFILE = 'cert_file'
CONF_TOPIC = 'topic'
diff --git a/homeassistant/components/apns/services.yaml b/homeassistant/components/apns/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py
index b265dc533eb..0ebe29ed47c 100644
--- a/homeassistant/components/apple_tv/__init__.py
+++ b/homeassistant/components/apple_tv/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyatv==0.3.12']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'apple_tv'
diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json
new file mode 100644
index 00000000000..f21de733376
--- /dev/null
+++ b/homeassistant/components/apple_tv/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "apple_tv",
+ "name": "Apple tv",
+ "documentation": "https://www.home-assistant.io/components/apple_tv",
+ "requirements": [
+ "pyatv==0.3.12"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py
index e00ce6ed13b..9698ef4c704 100644
--- a/homeassistant/components/apple_tv/media_player.py
+++ b/homeassistant/components/apple_tv/media_player.py
@@ -14,8 +14,6 @@ import homeassistant.util.dt as dt_util
from . import ATTR_ATV, ATTR_POWER, DATA_APPLE_TV, DATA_ENTITIES
-DEPENDENCIES = ['apple_tv']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_APPLE_TV = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \
diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py
index 25b500ac09d..2839e3a5324 100644
--- a/homeassistant/components/apple_tv/remote.py
+++ b/homeassistant/components/apple_tv/remote.py
@@ -4,8 +4,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME
from . import ATTR_ATV, ATTR_POWER, DATA_APPLE_TV
-DEPENDENCIES = ['apple_tv']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/aqualogic/__init__.py b/homeassistant/components/aqualogic/__init__.py
index a4f83b573f7..65718463218 100644
--- a/homeassistant/components/aqualogic/__init__.py
+++ b/homeassistant/components/aqualogic/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import (CONF_HOST, CONF_PORT,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
-REQUIREMENTS = ["aqualogic==1.0"]
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'aqualogic'
diff --git a/homeassistant/components/aqualogic/manifest.json b/homeassistant/components/aqualogic/manifest.json
new file mode 100644
index 00000000000..40f1805d83a
--- /dev/null
+++ b/homeassistant/components/aqualogic/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "aqualogic",
+ "name": "Aqualogic",
+ "documentation": "https://www.home-assistant.io/components/aqualogic",
+ "requirements": [
+ "aqualogic==1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py
index dc06a2127e9..454cdbd7f6b 100644
--- a/homeassistant/components/aqualogic/sensor.py
+++ b/homeassistant/components/aqualogic/sensor.py
@@ -14,8 +14,6 @@ from . import DOMAIN, UPDATE_TOPIC
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['aqualogic']
-
TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT]
PERCENT_UNITS = ['%', '%']
SALT_UNITS = ['g/L', 'PPM']
diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py
index 21e573f944b..b8bd8e41244 100644
--- a/homeassistant/components/aqualogic/switch.py
+++ b/homeassistant/components/aqualogic/switch.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN, UPDATE_TOPIC
-DEPENDENCIES = ['aqualogic']
-
_LOGGER = logging.getLogger(__name__)
SWITCH_TYPES = {
diff --git a/homeassistant/components/aquostv/manifest.json b/homeassistant/components/aquostv/manifest.json
new file mode 100644
index 00000000000..16865905ae9
--- /dev/null
+++ b/homeassistant/components/aquostv/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "aquostv",
+ "name": "Aquostv",
+ "documentation": "https://www.home-assistant.io/components/aquostv",
+ "requirements": [
+ "sharp_aquos_rc==0.3.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/aquostv/media_player.py b/homeassistant/components/aquostv/media_player.py
index 59723b47522..a4e88f02a59 100644
--- a/homeassistant/components/aquostv/media_player.py
+++ b/homeassistant/components/aquostv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with an Aquos TV.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.aquostv/
-"""
+"""Support for interface with an Aquos TV."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.const import (
CONF_USERNAME, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['sharp_aquos_rc==0.3.2']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Sharp Aquos TV'
diff --git a/homeassistant/components/arduino/__init__.py b/homeassistant/components/arduino/__init__.py
index 351122e74f0..a6841e07564 100644
--- a/homeassistant/components/arduino/__init__.py
+++ b/homeassistant/components/arduino/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
from homeassistant.const import CONF_PORT
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['PyMata==2.14']
-
_LOGGER = logging.getLogger(__name__)
BOARD = None
diff --git a/homeassistant/components/arduino/manifest.json b/homeassistant/components/arduino/manifest.json
new file mode 100644
index 00000000000..cf21cbe87ea
--- /dev/null
+++ b/homeassistant/components/arduino/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "arduino",
+ "name": "Arduino",
+ "documentation": "https://www.home-assistant.io/components/arduino",
+ "requirements": [
+ "PyMata==2.14"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/arduino/sensor.py b/homeassistant/components/arduino/sensor.py
index ff758ea5847..0cc6e006b89 100644
--- a/homeassistant/components/arduino/sensor.py
+++ b/homeassistant/components/arduino/sensor.py
@@ -14,8 +14,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_PINS = 'pins'
CONF_TYPE = 'analog'
-DEPENDENCIES = ['arduino']
-
PIN_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
})
diff --git a/homeassistant/components/arduino/switch.py b/homeassistant/components/arduino/switch.py
index 947c5188766..92e91196a9a 100644
--- a/homeassistant/components/arduino/switch.py
+++ b/homeassistant/components/arduino/switch.py
@@ -8,8 +8,6 @@ from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
-DEPENDENCIES = ['arduino']
-
_LOGGER = logging.getLogger(__name__)
CONF_PINS = 'pins'
diff --git a/homeassistant/components/arest/binary_sensor.py b/homeassistant/components/arest/binary_sensor.py
index b70620df3e2..3fd669a2bba 100644
--- a/homeassistant/components/arest/binary_sensor.py
+++ b/homeassistant/components/arest/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for an exposed aREST RESTful API of a device.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.arest/
-"""
+"""Support for an exposed aREST RESTful API of a device."""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/arest/manifest.json b/homeassistant/components/arest/manifest.json
new file mode 100644
index 00000000000..d5bcf92a39d
--- /dev/null
+++ b/homeassistant/components/arest/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "arest",
+ "name": "Arest",
+ "documentation": "https://www.home-assistant.io/components/arest",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py
index e0c5ef129ce..fc443cd60b6 100644
--- a/homeassistant/components/arest/sensor.py
+++ b/homeassistant/components/arest/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for an exposed aREST RESTful API of a device.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.arest/
-"""
+"""Support for an exposed aREST RESTful API of a device."""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/arest/switch.py b/homeassistant/components/arest/switch.py
index ab445db10d8..717acc2f336 100644
--- a/homeassistant/components/arest/switch.py
+++ b/homeassistant/components/arest/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for an exposed aREST RESTful API of a device.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.arest/
-"""
+"""Support for an exposed aREST RESTful API of a device."""
import logging
diff --git a/homeassistant/components/arlo/__init__.py b/homeassistant/components/arlo/__init__.py
index cbb720778e5..38230c2f05f 100644
--- a/homeassistant/components/arlo/__init__.py
+++ b/homeassistant/components/arlo/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import (
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
-REQUIREMENTS = ['pyarlo==0.2.3']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by arlo.netgear.com"
diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py
index 3557ed125c6..a7addfb86ea 100644
--- a/homeassistant/components/arlo/alarm_control_panel.py
+++ b/homeassistant/components/arlo/alarm_control_panel.py
@@ -22,8 +22,6 @@ CONF_HOME_MODE_NAME = 'home_mode_name'
CONF_AWAY_MODE_NAME = 'away_mode_name'
CONF_NIGHT_MODE_NAME = 'night_mode_name'
-DEPENDENCIES = ['arlo']
-
DISARMED = 'disarmed'
ICON = 'mdi:security'
diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py
index d4b00f00625..166e0781044 100644
--- a/homeassistant/components/arlo/camera.py
+++ b/homeassistant/components/arlo/camera.py
@@ -13,8 +13,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO
-DEPENDENCIES = ['arlo', 'ffmpeg']
-
_LOGGER = logging.getLogger(__name__)
ARLO_MODE_ARMED = 'armed'
diff --git a/homeassistant/components/arlo/manifest.json b/homeassistant/components/arlo/manifest.json
new file mode 100644
index 00000000000..35803d0d4f6
--- /dev/null
+++ b/homeassistant/components/arlo/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "arlo",
+ "name": "Arlo",
+ "documentation": "https://www.home-assistant.io/components/arlo",
+ "requirements": [
+ "pyarlo==0.2.3"
+ ],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py
index e08669eb80b..f83caec386b 100644
--- a/homeassistant/components/arlo/sensor.py
+++ b/homeassistant/components/arlo/sensor.py
@@ -17,8 +17,6 @@ from . import ATTRIBUTION, DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['arlo']
-
# sensor_type [ description, unit, icon ]
SENSOR_TYPES = {
'last_capture': ['Last', None, 'run-fast'],
diff --git a/homeassistant/components/arlo/services.yaml b/homeassistant/components/arlo/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/aruba/device_tracker.py b/homeassistant/components/aruba/device_tracker.py
index 142842b12d2..cde144e68f6 100644
--- a/homeassistant/components/aruba/device_tracker.py
+++ b/homeassistant/components/aruba/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Aruba Access Points.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.aruba/
-"""
+"""Support for Aruba Access Points."""
import logging
import re
@@ -16,8 +11,6 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pexpect==4.6.0']
-
_DEVICES_REGEX = re.compile(
r'(?P([^\s]+)?)\s+' +
r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
diff --git a/homeassistant/components/aruba/manifest.json b/homeassistant/components/aruba/manifest.json
new file mode 100644
index 00000000000..597975619e6
--- /dev/null
+++ b/homeassistant/components/aruba/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "aruba",
+ "name": "Aruba",
+ "documentation": "https://www.home-assistant.io/components/aruba",
+ "requirements": [
+ "pexpect==4.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/arwn/manifest.json b/homeassistant/components/arwn/manifest.json
new file mode 100644
index 00000000000..1c861aa67e2
--- /dev/null
+++ b/homeassistant/components/arwn/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "arwn",
+ "name": "Arwn",
+ "documentation": "https://www.home-assistant.io/components/arwn",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py
index 95825f4ca13..94b552c6eba 100644
--- a/homeassistant/components/arwn/sensor.py
+++ b/homeassistant/components/arwn/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for collecting data from the ARWN project.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.arwn/
-"""
+"""Support for collecting data from the ARWN project."""
import json
import logging
@@ -15,7 +10,6 @@ from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
DOMAIN = 'arwn'
DATA_ARWN = 'arwn'
diff --git a/homeassistant/components/asterisk_cdr/mailbox.py b/homeassistant/components/asterisk_cdr/mailbox.py
index db5d4e8d6ee..647067b60d4 100644
--- a/homeassistant/components/asterisk_cdr/mailbox.py
+++ b/homeassistant/components/asterisk_cdr/mailbox.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['asterisk_mbox']
-
MAILBOX_NAME = 'asterisk_cdr'
diff --git a/homeassistant/components/asterisk_cdr/manifest.json b/homeassistant/components/asterisk_cdr/manifest.json
new file mode 100644
index 00000000000..db1308b0483
--- /dev/null
+++ b/homeassistant/components/asterisk_cdr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "asterisk_cdr",
+ "name": "Asterisk cdr",
+ "documentation": "https://www.home-assistant.io/components/asterisk_cdr",
+ "requirements": [],
+ "dependencies": [
+ "asterisk_mbox"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/asterisk_mbox/__init__.py b/homeassistant/components/asterisk_mbox/__init__.py
index d8d3b194cd7..a354226bbc0 100644
--- a/homeassistant/components/asterisk_mbox/__init__.py
+++ b/homeassistant/components/asterisk_mbox/__init__.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_send, dispatcher_connect)
-REQUIREMENTS = ['asterisk_mbox==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'asterisk_mbox'
diff --git a/homeassistant/components/asterisk_mbox/mailbox.py b/homeassistant/components/asterisk_mbox/mailbox.py
index a3e7c3f4d61..f79c8922214 100644
--- a/homeassistant/components/asterisk_mbox/mailbox.py
+++ b/homeassistant/components/asterisk_mbox/mailbox.py
@@ -10,8 +10,6 @@ from . import DOMAIN as ASTERISK_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['asterisk_mbox']
-
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
diff --git a/homeassistant/components/asterisk_mbox/manifest.json b/homeassistant/components/asterisk_mbox/manifest.json
new file mode 100644
index 00000000000..bafe43c480f
--- /dev/null
+++ b/homeassistant/components/asterisk_mbox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "asterisk_mbox",
+ "name": "Asterisk mbox",
+ "documentation": "https://www.home-assistant.io/components/asterisk_mbox",
+ "requirements": [
+ "asterisk_mbox==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py
index 9b004b5bc04..cc51a15f8e8 100644
--- a/homeassistant/components/asuswrt/__init__.py
+++ b/homeassistant/components/asuswrt/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ['aioasuswrt==1.1.21']
-
_LOGGER = logging.getLogger(__name__)
CONF_PUB_KEY = 'pub_key'
diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py
index f5c6dd4a42a..68641f670aa 100644
--- a/homeassistant/components/asuswrt/device_tracker.py
+++ b/homeassistant/components/asuswrt/device_tracker.py
@@ -1,17 +1,10 @@
-"""
-Support for ASUSWRT routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.asuswrt/
-"""
+"""Support for ASUSWRT routers."""
import logging
from homeassistant.components.device_tracker import DeviceScanner
from . import DATA_ASUSWRT
-DEPENDENCIES = ['asuswrt']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json
new file mode 100644
index 00000000000..f36819f133d
--- /dev/null
+++ b/homeassistant/components/asuswrt/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "asuswrt",
+ "name": "Asuswrt",
+ "documentation": "https://www.home-assistant.io/components/asuswrt",
+ "requirements": [
+ "aioasuswrt==1.1.21"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@kennedyshead"
+ ]
+}
diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py
index 53d232862c6..8ae629bd12d 100644
--- a/homeassistant/components/asuswrt/sensor.py
+++ b/homeassistant/components/asuswrt/sensor.py
@@ -1,17 +1,10 @@
-"""
-Asuswrt status sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.asuswrt/
-"""
+"""Asuswrt status sensors."""
import logging
from homeassistant.helpers.entity import Entity
from . import DATA_ASUSWRT
-DEPENDENCIES = ['asuswrt']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py
index 8e749dca46e..e18c25706c1 100644
--- a/homeassistant/components/august/__init__.py
+++ b/homeassistant/components/august/__init__.py
@@ -15,8 +15,6 @@ _LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
-REQUIREMENTS = ['py-august==0.7.0']
-
DEFAULT_TIMEOUT = 10
ACTIVITY_FETCH_LIMIT = 10
ACTIVITY_INITIAL_FETCH_LIMIT = 20
diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py
index 3a69d41177d..d1f69645802 100644
--- a/homeassistant/components/august/binary_sensor.py
+++ b/homeassistant/components/august/binary_sensor.py
@@ -8,8 +8,6 @@ from . import DATA_AUGUST
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['august']
-
SCAN_INTERVAL = timedelta(seconds=5)
diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py
index 53a9d78bc60..0bf8a28f904 100644
--- a/homeassistant/components/august/camera.py
+++ b/homeassistant/components/august/camera.py
@@ -7,8 +7,6 @@ from homeassistant.components.camera import Camera
from . import DATA_AUGUST, DEFAULT_TIMEOUT
-DEPENDENCIES = ['august']
-
SCAN_INTERVAL = timedelta(seconds=5)
diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py
index e112eaa2592..5ad2bdc3b5b 100644
--- a/homeassistant/components/august/lock.py
+++ b/homeassistant/components/august/lock.py
@@ -9,8 +9,6 @@ from . import DATA_AUGUST
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['august']
-
SCAN_INTERVAL = timedelta(seconds=5)
diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json
new file mode 100644
index 00000000000..e41491c4b0a
--- /dev/null
+++ b/homeassistant/components/august/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "august",
+ "name": "August",
+ "documentation": "https://www.home-assistant.io/components/august",
+ "requirements": [
+ "py-august==0.7.0"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py
index cfd683346ff..58546382a50 100644
--- a/homeassistant/components/aurora/binary_sensor.py
+++ b/homeassistant/components/aurora/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for aurora forecast data sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.aurora/
-"""
+"""Support for aurora forecast data sensor."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/aurora/manifest.json b/homeassistant/components/aurora/manifest.json
new file mode 100644
index 00000000000..56ba3fe9356
--- /dev/null
+++ b/homeassistant/components/aurora/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "aurora",
+ "name": "Aurora",
+ "documentation": "https://www.home-assistant.io/components/aurora",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/auth/.translations/ko.json b/homeassistant/components/auth/.translations/ko.json
index c5278c63f79..6c2e8988d83 100644
--- a/homeassistant/components/auth/.translations/ko.json
+++ b/homeassistant/components/auth/.translations/ko.json
@@ -26,7 +26,7 @@
"step": {
"init": {
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
- "title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
+ "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
}
},
"title": "TOTP (\uc2dc\uac04 \uae30\ubc18 OTP)"
diff --git a/homeassistant/components/auth/.translations/nn.json b/homeassistant/components/auth/.translations/nn.json
index 24d756f938b..346c1cfe0c7 100644
--- a/homeassistant/components/auth/.translations/nn.json
+++ b/homeassistant/components/auth/.translations/nn.json
@@ -6,7 +6,7 @@
},
"step": {
"init": {
- "description": "For \u00e5 aktivere tofaktorautentisering ved hjelp av tidsbaserte eingangspassord, skann QR-koden med autentiseringsappen din. Dersom du ikkje har ein, vil vi r\u00e5de deg til \u00e5 bruke anten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har skanna koda, skriv du inn den sekssifra koda fr\u00e5 appen din for \u00e5 stadfeste oppsettet. Dersom du har problemer med \u00e5 skanne QR-koda, gjer du eit manuelt oppsett med kode ** ` {code} ` **.",
+ "description": "For \u00e5 aktivere to-faktor-autentisering ved hjelp av tid-baserte eingangspassord, skann QR-koden med autentiseringsappen din. Dersom du ikkje har ein, vil vi r\u00e5de deg til \u00e5 bruke anten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har skanna koda, skriv du inn den sekssifra koda fr\u00e5 appen din for \u00e5 stadfeste oppsettet. Dersom du har problemer med \u00e5 skanne QR-koda, gjer du eit manuelt oppsett med kode ** ` {code} ` **.",
"title": "Konfigurer to-faktor-autentisering ved hjelp av TOTP"
}
},
diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py
index 19edfe5a618..f1deaf0cb85 100644
--- a/homeassistant/components/auth/__init__.py
+++ b/homeassistant/components/auth/__init__.py
@@ -78,20 +78,16 @@ The result payload likes
"result": {
"id": "USER_ID",
"name": "John Doe",
- "is_owner': true,
- "credentials": [
- {
- "auth_provider_type": "homeassistant",
- "auth_provider_id": null
- }
- ],
- "mfa_modules": [
- {
- "id": "totp",
- "name": "TOTP",
- "enabled": true,
- }
- ]
+ "is_owner": true,
+ "credentials": [{
+ "auth_provider_type": "homeassistant",
+ "auth_provider_id": null
+ }],
+ "mfa_modules": [{
+ "id": "totp",
+ "name": "TOTP",
+ "enabled": true
+ }]
}
}
@@ -142,8 +138,6 @@ from . import login_flow
from . import mfa_setup_flow
DOMAIN = 'auth'
-DEPENDENCIES = ['http']
-
WS_TYPE_CURRENT_USER = 'auth/current_user'
SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_CURRENT_USER,
diff --git a/homeassistant/components/auth/manifest.json b/homeassistant/components/auth/manifest.json
new file mode 100644
index 00000000000..10be545f5e1
--- /dev/null
+++ b/homeassistant/components/auth/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "auth",
+ "name": "Auth",
+ "documentation": "https://www.home-assistant.io/components/auth",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/automatic/device_tracker.py b/homeassistant/components/automatic/device_tracker.py
index 9f20eb6d493..04e069a04f9 100644
--- a/homeassistant/components/automatic/device_tracker.py
+++ b/homeassistant/components/automatic/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for the Automatic platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.automatic/
-"""
+"""Support for the Automatic platform."""
import asyncio
from datetime import timedelta
import json
@@ -23,8 +18,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
-REQUIREMENTS = ['aioautomatic==0.6.5']
-
_LOGGER = logging.getLogger(__name__)
ATTR_FUEL_LEVEL = 'fuel_level'
@@ -39,8 +32,6 @@ DATA_CONFIGURING = 'automatic_configurator_clients'
DATA_REFRESH_TOKEN = 'refresh_token'
DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile']
DEFAULT_TIMEOUT = 5
-DEPENDENCIES = ['http']
-
EVENT_AUTOMATIC_UPDATE = 'automatic_update'
FULL_SCOPE = DEFAULT_SCOPE + ['current_location']
diff --git a/homeassistant/components/automatic/manifest.json b/homeassistant/components/automatic/manifest.json
new file mode 100644
index 00000000000..9743835af20
--- /dev/null
+++ b/homeassistant/components/automatic/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "automatic",
+ "name": "Automatic",
+ "documentation": "https://www.home-assistant.io/components/automatic",
+ "requirements": [
+ "aioautomatic==0.6.5"
+ ],
+ "dependencies": [
+ "configurator",
+ "http"
+ ],
+ "codeowners": [
+ "@armills"
+ ]
+}
diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py
index 94776deefb0..fa8b77da768 100644
--- a/homeassistant/components/automation/__init__.py
+++ b/homeassistant/components/automation/__init__.py
@@ -18,11 +18,9 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.loader import bind_hass
-from homeassistant.setup import async_prepare_setup_platform
from homeassistant.util.dt import utcnow
DOMAIN = 'automation'
-DEPENDENCIES = ['group']
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
@@ -416,11 +414,8 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
}
for conf in trigger_configs:
- platform = await async_prepare_setup_platform(
- hass, config, DOMAIN, conf.get(CONF_PLATFORM))
-
- if platform is None:
- return None
+ platform = importlib.import_module('.{}'.format(conf[CONF_PLATFORM]),
+ __name__)
remove = await platform.async_trigger(hass, conf, action, info)
diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py
index 20c689d74cf..51ec5baccfd 100644
--- a/homeassistant/components/automation/litejet.py
+++ b/homeassistant/components/automation/litejet.py
@@ -9,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_utc_time
-DEPENDENCIES = ['litejet']
-
_LOGGER = logging.getLogger(__name__)
CONF_NUMBER = 'number'
diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json
new file mode 100644
index 00000000000..ea63d4ff98a
--- /dev/null
+++ b/homeassistant/components/automation/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "automation",
+ "name": "Automation",
+ "documentation": "https://www.home-assistant.io/components/automation",
+ "requirements": [],
+ "dependencies": [
+ "group",
+ "webhook"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py
index ff89cd47024..837a22362b5 100644
--- a/homeassistant/components/automation/mqtt.py
+++ b/homeassistant/components/automation/mqtt.py
@@ -8,8 +8,6 @@ from homeassistant.components import mqtt
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv
-DEPENDENCIES = ['mqtt']
-
CONF_ENCODING = 'encoding'
CONF_TOPIC = 'topic'
DEFAULT_ENCODING = 'utf-8'
diff --git a/homeassistant/components/automation/webhook.py b/homeassistant/components/automation/webhook.py
index f65b5cf885c..37cab3cb8c0 100644
--- a/homeassistant/components/automation/webhook.py
+++ b/homeassistant/components/automation/webhook.py
@@ -33,6 +33,7 @@ async def _handle_webhook(action, hass, webhook_id, request):
else:
result['data'] = await request.post()
+ result['query'] = request.query
hass.async_run_job(action, {'trigger': result})
diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py
index 617198b2c8c..b138b8bf61f 100644
--- a/homeassistant/components/avion/light.py
+++ b/homeassistant/components/avion/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Avion dimmers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.avion/
-"""
+"""Support for Avion dimmers."""
import importlib
import logging
import time
@@ -17,8 +12,6 @@ from homeassistant.const import (
CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['avion==0.10']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_AVION_LED = SUPPORT_BRIGHTNESS
diff --git a/homeassistant/components/avion/manifest.json b/homeassistant/components/avion/manifest.json
new file mode 100644
index 00000000000..e7d97f13313
--- /dev/null
+++ b/homeassistant/components/avion/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "avion",
+ "name": "Avion",
+ "documentation": "https://www.home-assistant.io/components/avion",
+ "requirements": [
+ "avion==0.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/awair/manifest.json b/homeassistant/components/awair/manifest.json
new file mode 100644
index 00000000000..cba11e8be1c
--- /dev/null
+++ b/homeassistant/components/awair/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "awair",
+ "name": "Awair",
+ "documentation": "https://www.home-assistant.io/components/awair",
+ "requirements": [
+ "python_awair==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py
index 9a45cb66a86..85f18e87d13 100644
--- a/homeassistant/components/awair/sensor.py
+++ b/homeassistant/components/awair/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Awair indoor air quality monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.awair/
-"""
+"""Support for the Awair indoor air quality monitor."""
from datetime import timedelta
import logging
@@ -20,8 +15,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle, dt
-REQUIREMENTS = ['python_awair==0.0.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_SCORE = 'score'
diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py
index a15e56e9de8..e25af68d550 100644
--- a/homeassistant/components/aws/__init__.py
+++ b/homeassistant/components/aws/__init__.py
@@ -20,14 +20,13 @@ from .const import (
CONF_REGION,
CONF_SECRET_ACCESS_KEY,
CONF_SERVICE,
+ CONF_VALIDATE,
DATA_CONFIG,
DATA_HASS_CONFIG,
DATA_SESSIONS,
DOMAIN,
)
-REQUIREMENTS = ["aiobotocore==0.10.2"]
-
_LOGGER = logging.getLogger(__name__)
AWS_CREDENTIAL_SCHEMA = vol.Schema(
@@ -36,10 +35,15 @@ AWS_CREDENTIAL_SCHEMA = vol.Schema(
vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string,
vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string,
vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string,
+ vol.Optional(CONF_VALIDATE, default=True): cv.boolean,
}
)
-DEFAULT_CREDENTIAL = [{CONF_NAME: "default", CONF_PROFILE_NAME: "default"}]
+DEFAULT_CREDENTIAL = [{
+ CONF_NAME: "default",
+ CONF_PROFILE_NAME: "default",
+ CONF_VALIDATE: False,
+}]
SUPPORTED_SERVICES = ["lambda", "sns", "sqs"]
@@ -157,6 +161,7 @@ async def _validate_aws_credentials(hass, credential):
aws_config = credential.copy()
del aws_config[CONF_NAME]
+ del aws_config[CONF_VALIDATE]
profile = aws_config.get(CONF_PROFILE_NAME)
@@ -170,7 +175,8 @@ async def _validate_aws_credentials(hass, credential):
else:
session = aiobotocore.AioSession(loop=hass.loop)
- async with session.create_client("iam", **aws_config) as client:
- await client.get_user()
+ if credential[CONF_VALIDATE]:
+ async with session.create_client("iam", **aws_config) as client:
+ await client.get_user()
return session
diff --git a/homeassistant/components/aws/const.py b/homeassistant/components/aws/const.py
index 4fa88566934..4738547bdec 100644
--- a/homeassistant/components/aws/const.py
+++ b/homeassistant/components/aws/const.py
@@ -14,3 +14,4 @@ CONF_PROFILE_NAME = "profile_name"
CONF_REGION = "region_name"
CONF_SECRET_ACCESS_KEY = "aws_secret_access_key"
CONF_SERVICE = "service"
+CONF_VALIDATE = "validate"
diff --git a/homeassistant/components/aws/manifest.json b/homeassistant/components/aws/manifest.json
new file mode 100644
index 00000000000..a473a23f917
--- /dev/null
+++ b/homeassistant/components/aws/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "aws",
+ "name": "Aws",
+ "documentation": "https://www.home-assistant.io/components/aws",
+ "requirements": [
+ "aiobotocore==0.10.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@awarecan",
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py
index 48b80b64ce2..3a6193f403d 100644
--- a/homeassistant/components/aws/notify.py
+++ b/homeassistant/components/aws/notify.py
@@ -21,8 +21,6 @@ from .const import (
DATA_SESSIONS,
)
-DEPENDENCIES = ["aws"]
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/aws_lambda/__init__.py b/homeassistant/components/aws_lambda/__init__.py
deleted file mode 100644
index f6d86d02e93..00000000000
--- a/homeassistant/components/aws_lambda/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""The aws_lambda component."""
diff --git a/homeassistant/components/aws_lambda/notify.py b/homeassistant/components/aws_lambda/notify.py
deleted file mode 100644
index d7ebb40d19a..00000000000
--- a/homeassistant/components/aws_lambda/notify.py
+++ /dev/null
@@ -1,94 +0,0 @@
-"""
-AWS Lambda platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.aws_lambda/
-"""
-import base64
-import json
-import logging
-
-import voluptuous as vol
-
-from homeassistant.const import CONF_NAME, CONF_PLATFORM
-import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.json import JSONEncoder
-
-from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
- BaseNotificationService)
-
-REQUIREMENTS = ['boto3==1.9.16']
-
-_LOGGER = logging.getLogger(__name__)
-
-CONF_REGION = 'region_name'
-CONF_ACCESS_KEY_ID = 'aws_access_key_id'
-CONF_SECRET_ACCESS_KEY = 'aws_secret_access_key'
-CONF_PROFILE_NAME = 'profile_name'
-CONF_CONTEXT = 'context'
-ATTR_CREDENTIALS = 'credentials'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_REGION, default='us-east-1'): cv.string,
- vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string,
- vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string,
- vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string,
- vol.Optional(CONF_CONTEXT, default=dict()): vol.Coerce(dict)
-})
-
-
-def get_service(hass, config, discovery_info=None):
- """Get the AWS Lambda notification service."""
- _LOGGER.warning(
- "aws_lambda notify platform is deprecated, please replace it"
- " with aws component. This config will become invalid in version 0.92."
- " See https://www.home-assistant.io/components/aws/ for details."
- )
-
- context_str = json.dumps({'custom': config[CONF_CONTEXT]}, cls=JSONEncoder)
- context_b64 = base64.b64encode(context_str.encode('utf-8'))
- context = context_b64.decode('utf-8')
-
- import boto3
-
- aws_config = config.copy()
-
- del aws_config[CONF_PLATFORM]
- del aws_config[CONF_NAME]
- del aws_config[CONF_CONTEXT]
-
- profile = aws_config.get(CONF_PROFILE_NAME)
-
- if profile is not None:
- boto3.setup_default_session(profile_name=profile)
- del aws_config[CONF_PROFILE_NAME]
-
- lambda_client = boto3.client("lambda", **aws_config)
-
- return AWSLambda(lambda_client, context)
-
-
-class AWSLambda(BaseNotificationService):
- """Implement the notification service for the AWS Lambda service."""
-
- def __init__(self, lambda_client, context):
- """Initialize the service."""
- self.client = lambda_client
- self.context = context
-
- def send_message(self, message="", **kwargs):
- """Send notification to specified LAMBDA ARN."""
- targets = kwargs.get(ATTR_TARGET)
-
- if not targets:
- _LOGGER.info("At least 1 target is required")
- return
-
- for target in targets:
- cleaned_kwargs = dict((k, v) for k, v in kwargs.items() if v)
- payload = {"message": message}
- payload.update(cleaned_kwargs)
-
- self.client.invoke(FunctionName=target,
- Payload=json.dumps(payload),
- ClientContext=self.context)
diff --git a/homeassistant/components/aws_sns/__init__.py b/homeassistant/components/aws_sns/__init__.py
deleted file mode 100644
index b51698ce0dc..00000000000
--- a/homeassistant/components/aws_sns/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""The aws_sns component."""
diff --git a/homeassistant/components/aws_sns/notify.py b/homeassistant/components/aws_sns/notify.py
deleted file mode 100644
index 09018562cb8..00000000000
--- a/homeassistant/components/aws_sns/notify.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
-AWS SNS platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.aws_sns/
-"""
-import json
-import logging
-
-import voluptuous as vol
-
-from homeassistant.const import CONF_NAME, CONF_PLATFORM
-import homeassistant.helpers.config_validation as cv
-
-from homeassistant.components.notify import (
- ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA,
- BaseNotificationService)
-
-_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ["boto3==1.9.16"]
-
-CONF_REGION = 'region_name'
-CONF_ACCESS_KEY_ID = 'aws_access_key_id'
-CONF_SECRET_ACCESS_KEY = 'aws_secret_access_key'
-CONF_PROFILE_NAME = 'profile_name'
-ATTR_CREDENTIALS = 'credentials'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_REGION, default='us-east-1'): cv.string,
- vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string,
- vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string,
- vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string,
-})
-
-
-def get_service(hass, config, discovery_info=None):
- """Get the AWS SNS notification service."""
- _LOGGER.warning(
- "aws_sns notify platform is deprecated, please replace it"
- " with aws component. This config will become invalid in version 0.92."
- " See https://www.home-assistant.io/components/aws/ for details."
- )
-
- import boto3
-
- aws_config = config.copy()
-
- del aws_config[CONF_PLATFORM]
- del aws_config[CONF_NAME]
-
- profile = aws_config.get(CONF_PROFILE_NAME)
-
- if profile is not None:
- boto3.setup_default_session(profile_name=profile)
- del aws_config[CONF_PROFILE_NAME]
-
- sns_client = boto3.client("sns", **aws_config)
-
- return AWSSNS(sns_client)
-
-
-class AWSSNS(BaseNotificationService):
- """Implement the notification service for the AWS SNS service."""
-
- def __init__(self, sns_client):
- """Initialize the service."""
- self.client = sns_client
-
- def send_message(self, message="", **kwargs):
- """Send notification to specified SNS ARN."""
- targets = kwargs.get(ATTR_TARGET)
-
- if not targets:
- _LOGGER.info("At least 1 target is required")
- return
-
- message_attributes = {k: {"StringValue": json.dumps(v),
- "DataType": "String"}
- for k, v in kwargs.items() if v}
- for target in targets:
- self.client.publish(TargetArn=target, Message=message,
- Subject=kwargs.get(ATTR_TITLE,
- ATTR_TITLE_DEFAULT),
- MessageAttributes=message_attributes)
diff --git a/homeassistant/components/aws_sqs/__init__.py b/homeassistant/components/aws_sqs/__init__.py
deleted file mode 100644
index 79b29a76009..00000000000
--- a/homeassistant/components/aws_sqs/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""The aws_sqs component."""
diff --git a/homeassistant/components/aws_sqs/notify.py b/homeassistant/components/aws_sqs/notify.py
deleted file mode 100644
index eff9018bae9..00000000000
--- a/homeassistant/components/aws_sqs/notify.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""
-AWS SQS platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.aws_sqs/
-"""
-import json
-import logging
-
-import voluptuous as vol
-
-from homeassistant.const import CONF_NAME, CONF_PLATFORM
-import homeassistant.helpers.config_validation as cv
-
-from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
- BaseNotificationService)
-
-_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ["boto3==1.9.16"]
-
-CONF_REGION = 'region_name'
-CONF_ACCESS_KEY_ID = 'aws_access_key_id'
-CONF_SECRET_ACCESS_KEY = 'aws_secret_access_key'
-CONF_PROFILE_NAME = 'profile_name'
-ATTR_CREDENTIALS = 'credentials'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_REGION, default='us-east-1'): cv.string,
- vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string,
- vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string,
- vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string,
-})
-
-
-def get_service(hass, config, discovery_info=None):
- """Get the AWS SQS notification service."""
- _LOGGER.warning(
- "aws_sqs notify platform is deprecated, please replace it"
- " with aws component. This config will become invalid in version 0.92."
- " See https://www.home-assistant.io/components/aws/ for details."
- )
-
- import boto3
-
- aws_config = config.copy()
-
- del aws_config[CONF_PLATFORM]
- del aws_config[CONF_NAME]
-
- profile = aws_config.get(CONF_PROFILE_NAME)
-
- if profile is not None:
- boto3.setup_default_session(profile_name=profile)
- del aws_config[CONF_PROFILE_NAME]
-
- sqs_client = boto3.client("sqs", **aws_config)
-
- return AWSSQS(sqs_client)
-
-
-class AWSSQS(BaseNotificationService):
- """Implement the notification service for the AWS SQS service."""
-
- def __init__(self, sqs_client):
- """Initialize the service."""
- self.client = sqs_client
-
- def send_message(self, message="", **kwargs):
- """Send notification to specified SQS ARN."""
- targets = kwargs.get(ATTR_TARGET)
-
- if not targets:
- _LOGGER.info("At least 1 target is required")
- return
-
- for target in targets:
- cleaned_kwargs = dict((k, v) for k, v in kwargs.items() if v)
- message_body = {"message": message}
- message_body.update(cleaned_kwargs)
- message_attributes = {}
- for key, val in cleaned_kwargs.items():
- message_attributes[key] = {"StringValue": json.dumps(val),
- "DataType": "String"}
- self.client.send_message(QueueUrl=target,
- MessageBody=json.dumps(message_body),
- MessageAttributes=message_attributes)
diff --git a/homeassistant/components/axis/.translations/es-419.json b/homeassistant/components/axis/.translations/es-419.json
new file mode 100644
index 00000000000..1e9301a19da
--- /dev/null
+++ b/homeassistant/components/axis/.translations/es-419.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "El dispositivo ya est\u00e1 configurado",
+ "bad_config_file": "Datos err\u00f3neos del archivo de configuraci\u00f3n"
+ },
+ "error": {
+ "already_configured": "El dispositivo ya est\u00e1 configurado",
+ "device_unavailable": "El dispositivo no est\u00e1 disponible",
+ "faulty_credentials": "Credenciales de usuario incorrectas"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "password": "Contrase\u00f1a",
+ "port": "Puerto",
+ "username": "Nombre de usuario"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/es.json b/homeassistant/components/axis/.translations/es.json
new file mode 100644
index 00000000000..9229b90866f
--- /dev/null
+++ b/homeassistant/components/axis/.translations/es.json
@@ -0,0 +1,26 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "El dispositivo ya est\u00e1 configurado",
+ "bad_config_file": "Datos err\u00f3neos en el archivo de configuraci\u00f3n",
+ "link_local_address": "Las direcciones de enlace locales no son compatibles"
+ },
+ "error": {
+ "already_configured": "El dispositivo ya est\u00e1 configurado",
+ "device_unavailable": "El dispositivo no est\u00e1 disponible",
+ "faulty_credentials": "Credenciales de usuario incorrectas"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "host": "Host",
+ "password": "Contrase\u00f1a",
+ "port": "Puerto",
+ "username": "Nombre de usuario"
+ },
+ "title": "Configurar dispositivo Axis"
+ }
+ },
+ "title": "Dispositivo Axis"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/fr.json b/homeassistant/components/axis/.translations/fr.json
new file mode 100644
index 00000000000..020cd8f5946
--- /dev/null
+++ b/homeassistant/components/axis/.translations/fr.json
@@ -0,0 +1,26 @@
+{
+ "config": {
+ "abort": {
+ "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"
+ },
+ "error": {
+ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
+ "device_unavailable": "L'appareil n'est pas disponible",
+ "faulty_credentials": "Mauvaises informations d'identification de l'utilisateur"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "host": "H\u00f4te",
+ "password": "Mot de passe",
+ "port": "Port",
+ "username": "Nom d'utilisateur"
+ },
+ "title": "Configurer l'appareil Axis"
+ }
+ },
+ "title": "Appareil Axis"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/it.json b/homeassistant/components/axis/.translations/it.json
new file mode 100644
index 00000000000..2141bf34942
--- /dev/null
+++ b/homeassistant/components/axis/.translations/it.json
@@ -0,0 +1,25 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
+ "bad_config_file": "Dati errati dal file di configurazione"
+ },
+ "error": {
+ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
+ "device_unavailable": "Il dispositivo non \u00e8 disponibile",
+ "faulty_credentials": "Credenziali utente non valide"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "host": "Host",
+ "password": "Password",
+ "port": "Porta",
+ "username": "Nome utente"
+ },
+ "title": "Impostazione del dispositivo Axis"
+ }
+ },
+ "title": "Dispositivo Axis"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/ko.json b/homeassistant/components/axis/.translations/ko.json
index f1543afbae8..aafa4fc1896 100644
--- a/homeassistant/components/axis/.translations/ko.json
+++ b/homeassistant/components/axis/.translations/ko.json
@@ -1,13 +1,13 @@
{
"config": {
"abort": {
- "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
+ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"bad_config_file": "\uad6c\uc131 \ud30c\uc77c\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"link_local_address": "\ub85c\uceec \uc8fc\uc18c \uc5f0\uacb0\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4"
},
"error": {
- "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
- "device_unavailable": "\uc7a5\uce58\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
+ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
+ "device_unavailable": "\uae30\uae30\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
"faulty_credentials": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
},
"step": {
@@ -18,9 +18,9 @@
"port": "\ud3ec\ud2b8",
"username": "\uc0ac\uc6a9\uc790 \uc774\ub984"
},
- "title": "Axis \uc7a5\uce58 \uc124\uc815"
+ "title": "Axis \uae30\uae30 \uc124\uc815"
}
},
- "title": "Axis \uc7a5\uce58"
+ "title": "Axis \uae30\uae30"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/lb.json b/homeassistant/components/axis/.translations/lb.json
index e0f6ebc1553..6b0728f4030 100644
--- a/homeassistant/components/axis/.translations/lb.json
+++ b/homeassistant/components/axis/.translations/lb.json
@@ -1,5 +1,10 @@
{
"config": {
+ "abort": {
+ "already_configured": "Apparat ass scho konfigur\u00e9iert",
+ "bad_config_file": "Feelerhaft Donn\u00e9e\u00eb aus der Konfiguratioun's Datei",
+ "link_local_address": "Lokal Link Adressen ginn net \u00ebnnerst\u00ebtzt"
+ },
"error": {
"already_configured": "Apparat ass scho konfigur\u00e9iert",
"device_unavailable": "Apparat ass net erreechbar",
diff --git a/homeassistant/components/axis/.translations/nn.json b/homeassistant/components/axis/.translations/nn.json
new file mode 100644
index 00000000000..33644469359
--- /dev/null
+++ b/homeassistant/components/axis/.translations/nn.json
@@ -0,0 +1,13 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "host": "Vert",
+ "password": "Passord",
+ "port": "Port"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/no.json b/homeassistant/components/axis/.translations/no.json
new file mode 100644
index 00000000000..94b5a1680b7
--- /dev/null
+++ b/homeassistant/components/axis/.translations/no.json
@@ -0,0 +1,26 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Enheten er allerede konfigurert",
+ "bad_config_file": "D\u00e5rlig data fra konfigurasjonsfilen",
+ "link_local_address": "Linking av lokale adresser st\u00f8ttes ikke"
+ },
+ "error": {
+ "already_configured": "Enheten er allerede konfigurert",
+ "device_unavailable": "Enheten er ikke tilgjengelig",
+ "faulty_credentials": "Ugyldig brukerlegitimasjon"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "host": "Vert",
+ "password": "Passord",
+ "port": "Port",
+ "username": "Brukernavn"
+ },
+ "title": "Sett opp Axis enhet"
+ }
+ },
+ "title": "Axis enhet"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/pt.json b/homeassistant/components/axis/.translations/pt.json
new file mode 100644
index 00000000000..77ce7025f70
--- /dev/null
+++ b/homeassistant/components/axis/.translations/pt.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "host": "Servidor",
+ "password": "Palavra-passe",
+ "port": "Porta",
+ "username": "Nome de Utilizador"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/sv.json b/homeassistant/components/axis/.translations/sv.json
new file mode 100644
index 00000000000..435a56632e8
--- /dev/null
+++ b/homeassistant/components/axis/.translations/sv.json
@@ -0,0 +1,25 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Enheten \u00e4r redan konfigurerad",
+ "bad_config_file": "Felaktig data fr\u00e5n config fil"
+ },
+ "error": {
+ "already_configured": "Enheten \u00e4r redan konfigurerad",
+ "device_unavailable": "Enheten \u00e4r inte tillg\u00e4nglig",
+ "faulty_credentials": "Felaktiga anv\u00e4ndaruppgifter"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "host": "V\u00e4rd",
+ "password": "L\u00f6senord",
+ "port": "Port",
+ "username": "Anv\u00e4ndarnamn"
+ },
+ "title": "Konfigurera Axis enhet"
+ }
+ },
+ "title": "Axis enhet"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/.translations/th.json b/homeassistant/components/axis/.translations/th.json
new file mode 100644
index 00000000000..4226d4ddb3e
--- /dev/null
+++ b/homeassistant/components/axis/.translations/th.json
@@ -0,0 +1,13 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19",
+ "port": "Port",
+ "username": "\u0e0a\u0e37\u0e48\u0e2d\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py
index 53087f2682c..e9e8a158a3b 100644
--- a/homeassistant/components/axis/__init__.py
+++ b/homeassistant/components/axis/__init__.py
@@ -4,16 +4,14 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
- CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_TRIGGER_TIME,
+ CONF_DEVICE, CONF_MAC, CONF_NAME, CONF_TRIGGER_TIME,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
-from .config_flow import configured_devices, DEVICE_SCHEMA
+from .config_flow import DEVICE_SCHEMA
from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN
from .device import AxisNetworkDevice, get_device
-REQUIREMENTS = ['axis==17']
-
CONFIG_SCHEMA = vol.Schema({
DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA),
}, extra=vol.ALLOW_EXTRA)
@@ -21,18 +19,17 @@ CONFIG_SCHEMA = vol.Schema({
async def async_setup(hass, config):
"""Set up for Axis devices."""
- if DOMAIN in config:
+ if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
for device_name, device_config in config[DOMAIN].items():
if CONF_NAME not in device_config:
device_config[CONF_NAME] = device_name
- if device_config[CONF_HOST] not in configured_devices(hass):
- hass.async_create_task(hass.config_entries.flow.async_init(
- DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
- data=device_config
- ))
+ hass.async_create_task(hass.config_entries.flow.async_init(
+ DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
+ data=device_config
+ ))
return True
@@ -59,14 +56,17 @@ async def async_setup_entry(hass, config_entry):
return True
+async def async_unload_entry(hass, config_entry):
+ """Unload Axis device config entry."""
+ device = hass.data[DOMAIN].pop(config_entry.data[CONF_MAC])
+ return await device.async_reset()
+
+
async def async_populate_options(hass, config_entry):
"""Populate default options for device."""
- from axis.vapix import VAPIX_IMAGE_FORMAT
-
device = await get_device(hass, config_entry.data[CONF_DEVICE])
- supported_formats = device.vapix.get_param(VAPIX_IMAGE_FORMAT)
-
+ supported_formats = device.vapix.params.image_format
camera = bool(supported_formats)
options = {
diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py
index 6d373dd638f..e9ef9f63710 100644
--- a/homeassistant/components/axis/binary_sensor.py
+++ b/homeassistant/components/axis/binary_sensor.py
@@ -11,8 +11,6 @@ from homeassistant.util.dt import utcnow
from .const import DOMAIN as AXIS_DOMAIN, LOGGER
-DEPENDENCIES = [AXIS_DOMAIN]
-
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up a Axis binary sensor."""
@@ -20,8 +18,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device = hass.data[AXIS_DOMAIN][serial_number]
@callback
- def async_add_sensor(event):
+ def async_add_sensor(event_id):
"""Add binary sensor from Axis device."""
+ event = device.api.event.events[event_id]
async_add_entities([AxisBinarySensor(event, device)], True)
device.listeners.append(async_dispatcher_connect(
@@ -35,23 +34,34 @@ class AxisBinarySensor(BinarySensorDevice):
"""Initialize the Axis binary sensor."""
self.event = event
self.device = device
- self.delay = device.config_entry.options[CONF_TRIGGER_TIME]
self.remove_timer = None
+ self.unsub_dispatcher = None
async def async_added_to_hass(self):
"""Subscribe sensors events."""
self.event.register_callback(self.update_callback)
+ self.unsub_dispatcher = async_dispatcher_connect(
+ self.hass, self.device.event_reachable, self.update_callback)
- def update_callback(self):
- """Update the sensor's state, if needed."""
+ async def async_will_remove_from_hass(self) -> None:
+ """Disconnect device object when removed."""
+ self.event.remove_callback(self.update_callback)
+ self.unsub_dispatcher()
+
+ @callback
+ def update_callback(self, no_delay=False):
+ """Update the sensor's state, if needed.
+
+ Parameter no_delay is True when device_event_reachable is sent.
+ """
delay = self.device.config_entry.options[CONF_TRIGGER_TIME]
if self.remove_timer is not None:
self.remove_timer()
self.remove_timer = None
- if delay == 0 or self.is_on:
- self.schedule_update_ha_state()
+ if self.is_on or delay == 0 or no_delay:
+ self.async_schedule_update_ha_state()
return
@callback
@@ -74,12 +84,12 @@ class AxisBinarySensor(BinarySensorDevice):
def name(self):
"""Return the name of the event."""
return '{} {} {}'.format(
- self.device.name, self.event.event_type, self.event.id)
+ self.device.name, self.event.TYPE, self.event.id)
@property
def device_class(self):
"""Return the class of the event."""
- return self.event.event_class
+ return self.event.CLASS
@property
def unique_id(self):
@@ -87,6 +97,10 @@ class AxisBinarySensor(BinarySensorDevice):
return '{}-{}-{}'.format(
self.device.serial, self.event.topic, self.event.id)
+ def available(self):
+ """Return True if device is available."""
+ return self.device.available
+
@property
def should_poll(self):
"""No polling needed."""
diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py
index 45801257d00..457cc23e73d 100644
--- a/homeassistant/components/axis/camera.py
+++ b/homeassistant/components/axis/camera.py
@@ -1,18 +1,19 @@
"""Support for Axis camera streaming."""
+from homeassistant.components.camera import SUPPORT_STREAM
from homeassistant.components.mjpeg.camera import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging)
from homeassistant.const import (
CONF_AUTHENTICATION, CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
+from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN as AXIS_DOMAIN
-DEPENDENCIES = [AXIS_DOMAIN]
-
AXIS_IMAGE = 'http://{}:{}/axis-cgi/jpg/image.cgi'
AXIS_VIDEO = 'http://{}:{}/axis-cgi/mjpg/video.cgi'
+AXIS_STREAM = 'rtsp://{}:{}@{}/axis-media/media.amp?videocodec=h264'
async def async_setup_entry(hass, config_entry, async_add_entities):
@@ -46,17 +47,47 @@ class AxisCamera(MjpegCamera):
self.device_config = config
self.device = device
self.port = device.config_entry.data[CONF_DEVICE][CONF_PORT]
- self.unsub_dispatcher = None
+ self.unsub_dispatcher = []
async def async_added_to_hass(self):
"""Subscribe camera events."""
- self.unsub_dispatcher = async_dispatcher_connect(
- self.hass, 'axis_{}_new_ip'.format(self.device.name), self._new_ip)
+ self.unsub_dispatcher.append(async_dispatcher_connect(
+ self.hass, self.device.event_new_address, self._new_address))
+ self.unsub_dispatcher.append(async_dispatcher_connect(
+ self.hass, self.device.event_reachable, self.update_callback))
- def _new_ip(self, host):
- """Set new IP for video stream."""
- self._mjpeg_url = AXIS_VIDEO.format(host, self.port)
- self._still_image_url = AXIS_IMAGE.format(host, self.port)
+ async def async_will_remove_from_hass(self) -> None:
+ """Disconnect device object when removed."""
+ for unsub_dispatcher in self.unsub_dispatcher:
+ unsub_dispatcher()
+
+ @property
+ def supported_features(self):
+ """Return supported features."""
+ return SUPPORT_STREAM
+
+ @property
+ def stream_source(self):
+ """Return the stream source."""
+ return AXIS_STREAM.format(
+ self.device.config_entry.data[CONF_DEVICE][CONF_USERNAME],
+ self.device.config_entry.data[CONF_DEVICE][CONF_PASSWORD],
+ self.device.host)
+
+ @callback
+ def update_callback(self, no_delay=None):
+ """Update the cameras state."""
+ self.async_schedule_update_ha_state()
+
+ @property
+ def available(self):
+ """Return True if device is available."""
+ return self.device.available
+
+ def _new_address(self):
+ """Set new device address for video stream."""
+ self._mjpeg_url = AXIS_VIDEO.format(self.device.host, self.port)
+ self._still_image_url = AXIS_IMAGE.format(self.device.host, self.port)
@property
def unique_id(self):
diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py
index 24c286b140a..0c175de20c7 100644
--- a/homeassistant/components/axis/config_flow.py
+++ b/homeassistant/components/axis/config_flow.py
@@ -40,8 +40,8 @@ DEVICE_SCHEMA = vol.Schema({
@callback
def configured_devices(hass):
"""Return a set of the configured devices."""
- return set(entry.data[CONF_DEVICE][CONF_HOST] for entry
- in hass.config_entries.async_entries(DOMAIN))
+ return {entry.data[CONF_MAC]: entry for entry
+ in hass.config_entries.async_entries(DOMAIN)}
@config_entries.HANDLERS.register(DOMAIN)
@@ -66,14 +66,10 @@ class AxisFlowHandler(config_entries.ConfigFlow):
Manage device specific parameters.
"""
- from axis.vapix import VAPIX_MODEL_ID, VAPIX_SERIAL_NUMBER
errors = {}
if user_input is not None:
try:
- if user_input[CONF_HOST] in configured_devices(self.hass):
- raise AlreadyConfigured
-
self.device_config = {
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
@@ -82,9 +78,12 @@ class AxisFlowHandler(config_entries.ConfigFlow):
}
device = await get_device(self.hass, self.device_config)
- self.serial_number = device.vapix.get_param(
- VAPIX_SERIAL_NUMBER)
- self.model = device.vapix.get_param(VAPIX_MODEL_ID)
+ self.serial_number = device.vapix.params.system_serialnumber
+
+ if self.serial_number in configured_devices(self.hass):
+ raise AlreadyConfigured
+
+ self.model = device.vapix.params.prodnbr
return await self._create_entry()
@@ -142,22 +141,30 @@ class AxisFlowHandler(config_entries.ConfigFlow):
data=data
)
+ async def _update_entry(self, entry, host):
+ """Update existing entry if it is the same device."""
+ entry.data[CONF_DEVICE][CONF_HOST] = host
+ self.hass.config_entries.async_update_entry(entry)
+
async def async_step_discovery(self, discovery_info):
"""Prepare configuration for a discovered Axis device.
This flow is triggered by the discovery component.
"""
- if discovery_info[CONF_HOST] in configured_devices(self.hass):
- return self.async_abort(reason='already_configured')
-
if discovery_info[CONF_HOST].startswith('169.254'):
return self.async_abort(reason='link_local_address')
+ serialnumber = discovery_info['properties']['macaddress']
+ device_entries = configured_devices(self.hass)
+
+ if serialnumber in device_entries:
+ entry = device_entries[serialnumber]
+ await self._update_entry(entry, discovery_info[CONF_HOST])
+ return self.async_abort(reason='already_configured')
+
config_file = await self.hass.async_add_executor_job(
load_json, self.hass.config.path(CONFIG_FILE))
- serialnumber = discovery_info['properties']['macaddress']
-
if serialnumber not in config_file:
self.discovery_schema = {
vol.Required(
diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py
index ffe48e5f733..1595dde4cba 100644
--- a/homeassistant/components/axis/device.py
+++ b/homeassistant/components/axis/device.py
@@ -12,6 +12,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER
+
from .errors import AuthenticationRequired, CannotConnect
@@ -66,14 +67,9 @@ class AxisNetworkDevice:
async def async_setup(self):
"""Set up the device."""
- from axis.vapix import VAPIX_FW_VERSION, VAPIX_PROD_TYPE
-
- hass = self.hass
-
try:
self.api = await get_device(
- hass, self.config_entry.data[CONF_DEVICE],
- event_types='on', signal_callback=self.async_signal_callback)
+ self.hass, self.config_entry.data[CONF_DEVICE])
except CannotConnect:
raise ConfigEntryNotReady
@@ -83,8 +79,8 @@ class AxisNetworkDevice:
'Unknown error connecting with Axis device on %s', self.host)
return False
- self.fw_version = self.api.vapix.get_param(VAPIX_FW_VERSION)
- self.product_type = self.api.vapix.get_param(VAPIX_PROD_TYPE)
+ self.fw_version = self.api.vapix.params.firmware_version
+ self.product_type = self.api.vapix.params.prodtype
if self.config_entry.options[CONF_CAMERA]:
self.hass.async_create_task(
@@ -92,31 +88,93 @@ class AxisNetworkDevice:
self.config_entry, 'camera'))
if self.config_entry.options[CONF_EVENTS]:
- self.hass.async_create_task(
+ task = self.hass.async_create_task(
self.hass.config_entries.async_forward_entry_setup(
self.config_entry, 'binary_sensor'))
- self.api.start()
+
+ self.api.stream.connection_status_callback = \
+ self.async_connection_status_callback
+ self.api.enable_events(event_callback=self.async_event_callback)
+ task.add_done_callback(self.start)
+
+ self.config_entry.add_update_listener(self.async_new_address_callback)
return True
+ @property
+ def event_new_address(self):
+ """Device specific event to signal new device address."""
+ return 'axis_new_address_{}'.format(self.serial)
+
+ @staticmethod
+ async def async_new_address_callback(hass, entry):
+ """Handle signals of device getting new address.
+
+ This is a static method because a class method (bound method),
+ can not be used with weak references.
+ """
+ device = hass.data[DOMAIN][entry.data[CONF_MAC]]
+ device.api.config.host = device.host
+ async_dispatcher_send(hass, device.event_new_address)
+
+ @property
+ def event_reachable(self):
+ """Device specific event to signal a change in connection status."""
+ return 'axis_reachable_{}'.format(self.serial)
+
+ @callback
+ def async_connection_status_callback(self, status):
+ """Handle signals of device connection status.
+
+ This is called on every RTSP keep-alive message.
+ Only signal state change if state change is true.
+ """
+ from axis.streammanager import SIGNAL_PLAYING
+ if self.available != (status == SIGNAL_PLAYING):
+ self.available = not self.available
+ async_dispatcher_send(self.hass, self.event_reachable, True)
+
@property
def event_new_sensor(self):
"""Device specific event to signal new sensor available."""
return 'axis_add_sensor_{}'.format(self.serial)
@callback
- def async_signal_callback(self, action, event):
+ def async_event_callback(self, action, event_id):
"""Call to configure events when initialized on event stream."""
if action == 'add':
- async_dispatcher_send(self.hass, self.event_new_sensor, event)
+ async_dispatcher_send(self.hass, self.event_new_sensor, event_id)
+
+ @callback
+ def start(self, fut):
+ """Start the event stream."""
+ self.api.start()
@callback
def shutdown(self, event):
"""Stop the event stream."""
self.api.stop()
+ async def async_reset(self):
+ """Reset this device to default state."""
+ self.api.stop()
-async def get_device(hass, config, event_types=None, signal_callback=None):
+ if self.config_entry.options[CONF_CAMERA]:
+ await self.hass.config_entries.async_forward_entry_unload(
+ self.config_entry, 'camera')
+
+ if self.config_entry.options[CONF_EVENTS]:
+ await self.hass.config_entries.async_forward_entry_unload(
+ self.config_entry, 'binary_sensor')
+
+ for unsub_dispatcher in self.listeners:
+ unsub_dispatcher()
+ self.listeners = []
+
+ return True
+
+
+async def get_device(hass, config):
"""Create a Axis device."""
import axis
@@ -124,12 +182,16 @@ async def get_device(hass, config, event_types=None, signal_callback=None):
loop=hass.loop, host=config[CONF_HOST],
username=config[CONF_USERNAME],
password=config[CONF_PASSWORD],
- port=config[CONF_PORT], web_proto='http',
- event_types=event_types, signal=signal_callback)
+ port=config[CONF_PORT], web_proto='http')
+
+ device.vapix.initialize_params(preload_data=False)
try:
with async_timeout.timeout(15):
- await hass.async_add_executor_job(device.vapix.load_params)
+ await hass.async_add_executor_job(
+ device.vapix.params.update_brand)
+ await hass.async_add_executor_job(
+ device.vapix.params.update_properties)
return device
except axis.Unauthorized:
diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json
new file mode 100644
index 00000000000..f87718bfddd
--- /dev/null
+++ b/homeassistant/components/axis/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "axis",
+ "name": "Axis",
+ "documentation": "https://www.home-assistant.io/components/axis",
+ "requirements": ["axis==22"],
+ "dependencies": [],
+ "codeowners": ["@kane610"]
+}
diff --git a/homeassistant/components/baidu/manifest.json b/homeassistant/components/baidu/manifest.json
new file mode 100644
index 00000000000..1dea1b7e37b
--- /dev/null
+++ b/homeassistant/components/baidu/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "baidu",
+ "name": "Baidu",
+ "documentation": "https://www.home-assistant.io/components/baidu",
+ "requirements": [
+ "baidu-aip==1.6.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/baidu/tts.py b/homeassistant/components/baidu/tts.py
index 07b69d41dfd..faf62e92651 100644
--- a/homeassistant/components/baidu/tts.py
+++ b/homeassistant/components/baidu/tts.py
@@ -1,10 +1,4 @@
-"""
-Support for Baidu speech service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts.baidu/
-"""
-
+"""Support for Baidu speech service."""
import logging
import voluptuous as vol
@@ -13,8 +7,6 @@ from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ["baidu-aip==1.6.6"]
-
_LOGGER = logging.getLogger(__name__)
SUPPORTED_LANGUAGES = ['zh']
diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py
index 97889ea7497..6b2395ef6d2 100644
--- a/homeassistant/components/bayesian/binary_sensor.py
+++ b/homeassistant/components/bayesian/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Use Bayesian Inference to trigger a binary sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.bayesian/
-"""
+"""Use Bayesian Inference to trigger a binary sensor."""
from collections import OrderedDict
import voluptuous as vol
diff --git a/homeassistant/components/bayesian/manifest.json b/homeassistant/components/bayesian/manifest.json
new file mode 100644
index 00000000000..25480ac8bdc
--- /dev/null
+++ b/homeassistant/components/bayesian/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "bayesian",
+ "name": "Bayesian",
+ "documentation": "https://www.home-assistant.io/components/bayesian",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bbb_gpio/__init__.py b/homeassistant/components/bbb_gpio/__init__.py
index 7749af8f335..85ea5753739 100644
--- a/homeassistant/components/bbb_gpio/__init__.py
+++ b/homeassistant/components/bbb_gpio/__init__.py
@@ -4,8 +4,6 @@ import logging
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['Adafruit_BBIO==1.0.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'bbb_gpio'
diff --git a/homeassistant/components/bbb_gpio/binary_sensor.py b/homeassistant/components/bbb_gpio/binary_sensor.py
index 1ee371dcc2a..bcc45a4af32 100644
--- a/homeassistant/components/bbb_gpio/binary_sensor.py
+++ b/homeassistant/components/bbb_gpio/binary_sensor.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['bbb_gpio']
-
CONF_PINS = 'pins'
CONF_BOUNCETIME = 'bouncetime'
CONF_INVERT_LOGIC = 'invert_logic'
diff --git a/homeassistant/components/bbb_gpio/manifest.json b/homeassistant/components/bbb_gpio/manifest.json
new file mode 100644
index 00000000000..5632836bfbb
--- /dev/null
+++ b/homeassistant/components/bbb_gpio/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "bbb_gpio",
+ "name": "Bbb gpio",
+ "documentation": "https://www.home-assistant.io/components/bbb_gpio",
+ "requirements": [
+ "Adafruit_BBIO==1.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bbb_gpio/switch.py b/homeassistant/components/bbb_gpio/switch.py
index 3ad46fd61ae..49b4c5de19c 100644
--- a/homeassistant/components/bbb_gpio/switch.py
+++ b/homeassistant/components/bbb_gpio/switch.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['bbb_gpio']
-
CONF_PINS = 'pins'
CONF_INITIAL = 'initial'
CONF_INVERT_LOGIC = 'invert_logic'
diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py
index f59c922577b..f70969aa61b 100644
--- a/homeassistant/components/bbox/device_tracker.py
+++ b/homeassistant/components/bbox/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for French FAI Bouygues Bbox routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.bbox/
-"""
+"""Support for French FAI Bouygues Bbox routers."""
from collections import namedtuple
from datetime import timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pybbox==0.0.5-alpha']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = '192.168.1.254'
diff --git a/homeassistant/components/bbox/manifest.json b/homeassistant/components/bbox/manifest.json
new file mode 100644
index 00000000000..54cd9a3af64
--- /dev/null
+++ b/homeassistant/components/bbox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "bbox",
+ "name": "Bbox",
+ "documentation": "https://www.home-assistant.io/components/bbox",
+ "requirements": [
+ "pybbox==0.0.5-alpha"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py
index c81160dc2ae..80fa82b30fc 100644
--- a/homeassistant/components/bbox/sensor.py
+++ b/homeassistant/components/bbox/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Bbox Bouygues Modem Router.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.bbox/
-"""
+"""Support for Bbox Bouygues Modem Router."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pybbox==0.0.5-alpha']
-
_LOGGER = logging.getLogger(__name__)
BANDWIDTH_MEGABITS_SECONDS = 'Mb/s' # type: str
diff --git a/homeassistant/components/bh1750/manifest.json b/homeassistant/components/bh1750/manifest.json
new file mode 100644
index 00000000000..90e62c78356
--- /dev/null
+++ b/homeassistant/components/bh1750/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "bh1750",
+ "name": "Bh1750",
+ "documentation": "https://www.home-assistant.io/components/bh1750",
+ "requirements": [
+ "i2csense==0.0.4",
+ "smbus-cffi==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py
index 592a6acbe58..eaee023ce86 100644
--- a/homeassistant/components/bh1750/sensor.py
+++ b/homeassistant/components/bh1750/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for BH1750 light sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.bh1750/
-"""
+"""Support for BH1750 light sensor."""
from functools import partial
import logging
@@ -14,9 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME, DEVICE_CLASS_ILLUMINANCE
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['i2csense==0.0.4',
- 'smbus-cffi==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_I2C_ADDRESS = 'i2c_address'
diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py
index 9972e4dca3b..19054588ee7 100644
--- a/homeassistant/components/binary_sensor/__init__.py
+++ b/homeassistant/components/binary_sensor/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to interface with binary sensors.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor/
-"""
+"""Component to interface with binary sensors."""
from datetime import timedelta
import logging
@@ -20,30 +15,100 @@ DOMAIN = 'binary_sensor'
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
+
+# On means low, Off means normal
+DEVICE_CLASS_BATTERY = 'battery'
+
+# On means cold, Off means normal
+DEVICE_CLASS_COLD = 'cold'
+
+# On means connected, Off means disconnected
+DEVICE_CLASS_CONNECTIVITY = 'connectivity'
+
+# On means open, Off means closed
+DEVICE_CLASS_DOOR = 'door'
+
+# On means open, Off means closed
+DEVICE_CLASS_GARAGE_DOOR = 'garage_door'
+
+# On means gas detected, Off means no gas (clear)
+DEVICE_CLASS_GAS = 'gas'
+
+# On means hot, Off means normal
+DEVICE_CLASS_HEAT = 'heat'
+
+# On means light detected, Off means no light
+DEVICE_CLASS_LIGHT = 'light'
+
+# On means open (unlocked), Off means closed (locked)
+DEVICE_CLASS_LOCK = 'lock'
+
+# On means wet, Off means dry
+DEVICE_CLASS_MOISTURE = 'moisture'
+
+# On means motion detected, Off means no motion (clear)
+DEVICE_CLASS_MOTION = 'motion'
+
+# On means moving, Off means not moving (stopped)
+DEVICE_CLASS_MOVING = 'moving'
+
+# On means occupied, Off means not occupied (clear)
+DEVICE_CLASS_OCCUPANCY = 'occupancy'
+
+# On means open, Off means closed
+DEVICE_CLASS_OPENING = 'opening'
+
+# On means plugged in, Off means unplugged
+DEVICE_CLASS_PLUG = 'plug'
+
+# On means power detected, Off means no power
+DEVICE_CLASS_POWER = 'power'
+
+# On means home, Off means away
+DEVICE_CLASS_PRESENCE = 'presence'
+
+# On means problem detected, Off means no problem (OK)
+DEVICE_CLASS_PROBLEM = 'problem'
+
+# On means unsafe, Off means safe
+DEVICE_CLASS_SAFETY = 'safety'
+
+# On means smoke detected, Off means no smoke (clear)
+DEVICE_CLASS_SMOKE = 'smoke'
+
+# On means sound detected, Off means no sound (clear)
+DEVICE_CLASS_SOUND = 'sound'
+
+# On means vibration detected, Off means no vibration
+DEVICE_CLASS_VIBRATION = 'vibration'
+
+# On means open, Off means closed
+DEVICE_CLASS_WINDOW = 'window'
+
DEVICE_CLASSES = [
- 'battery', # On means low, Off means normal
- 'cold', # On means cold, Off means normal
- 'connectivity', # On means connected, Off means disconnected
- 'door', # On means open, Off means closed
- 'garage_door', # On means open, Off means closed
- 'gas', # On means gas detected, Off means no gas (clear)
- 'heat', # On means hot, Off means normal
- 'light', # On means light detected, Off means no light
- 'lock', # On means open (unlocked), Off means closed (locked)
- 'moisture', # On means wet, Off means dry
- 'motion', # On means motion detected, Off means no motion (clear)
- 'moving', # On means moving, Off means not moving (stopped)
- 'occupancy', # On means occupied, Off means not occupied (clear)
- 'opening', # On means open, Off means closed
- 'plug', # On means plugged in, Off means unplugged
- 'power', # On means power detected, Off means no power
- 'presence', # On means home, Off means away
- 'problem', # On means problem detected, Off means no problem (OK)
- 'safety', # On means unsafe, Off means safe
- 'smoke', # On means smoke detected, Off means no smoke (clear)
- 'sound', # On means sound detected, Off means no sound (clear)
- 'vibration', # On means vibration detected, Off means no vibration
- 'window', # On means open, Off means closed
+ DEVICE_CLASS_BATTERY,
+ DEVICE_CLASS_COLD,
+ DEVICE_CLASS_CONNECTIVITY,
+ DEVICE_CLASS_DOOR,
+ DEVICE_CLASS_GARAGE_DOOR,
+ DEVICE_CLASS_GAS,
+ DEVICE_CLASS_HEAT,
+ DEVICE_CLASS_LIGHT,
+ DEVICE_CLASS_LOCK,
+ DEVICE_CLASS_MOISTURE,
+ DEVICE_CLASS_MOTION,
+ DEVICE_CLASS_MOVING,
+ DEVICE_CLASS_OCCUPANCY,
+ DEVICE_CLASS_OPENING,
+ DEVICE_CLASS_PLUG,
+ DEVICE_CLASS_POWER,
+ DEVICE_CLASS_PRESENCE,
+ DEVICE_CLASS_PROBLEM,
+ DEVICE_CLASS_SAFETY,
+ DEVICE_CLASS_SMOKE,
+ DEVICE_CLASS_SOUND,
+ DEVICE_CLASS_VIBRATION,
+ DEVICE_CLASS_WINDOW,
]
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
diff --git a/homeassistant/components/binary_sensor/manifest.json b/homeassistant/components/binary_sensor/manifest.json
new file mode 100644
index 00000000000..d627351958d
--- /dev/null
+++ b/homeassistant/components/binary_sensor/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "binary_sensor",
+ "name": "Binary sensor",
+ "documentation": "https://www.home-assistant.io/components/binary_sensor",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bitcoin/manifest.json b/homeassistant/components/bitcoin/manifest.json
new file mode 100644
index 00000000000..85da99a6885
--- /dev/null
+++ b/homeassistant/components/bitcoin/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "bitcoin",
+ "name": "Bitcoin",
+ "documentation": "https://www.home-assistant.io/components/bitcoin",
+ "requirements": [
+ "blockchain==1.4.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py
index e654f29f42a..6ccb10f50e6 100644
--- a/homeassistant/components/bitcoin/sensor.py
+++ b/homeassistant/components/bitcoin/sensor.py
@@ -1,9 +1,4 @@
-"""
-Bitcoin information service that uses blockchain.info.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.bitcoin/
-"""
+"""Bitcoin information service that uses blockchain.info."""
import logging
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['blockchain==1.4.4']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by blockchain.info"
diff --git a/homeassistant/components/blackbird/manifest.json b/homeassistant/components/blackbird/manifest.json
new file mode 100644
index 00000000000..9e3e41290ea
--- /dev/null
+++ b/homeassistant/components/blackbird/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "blackbird",
+ "name": "Blackbird",
+ "documentation": "https://www.home-assistant.io/components/blackbird",
+ "requirements": [
+ "pyblackbird==0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/blackbird/media_player.py b/homeassistant/components/blackbird/media_player.py
index 2daa2656e83..be0538a89e9 100644
--- a/homeassistant/components/blackbird/media_player.py
+++ b/homeassistant/components/blackbird/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing with Monoprice Blackbird 4k 8x8 HDBaseT Matrix.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.blackbird
-"""
+"""Support for interfacing with Monoprice Blackbird 4k 8x8 HDBaseT Matrix."""
import logging
import socket
@@ -19,8 +14,6 @@ from homeassistant.const import (
STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyblackbird==0.5']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
diff --git a/homeassistant/components/blackbird/services.yaml b/homeassistant/components/blackbird/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py
index 488209e3689..397ee097cae 100644
--- a/homeassistant/components/blink/__init__.py
+++ b/homeassistant/components/blink/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.const import (
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
-REQUIREMENTS = ['blinkpy==0.13.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'blink'
diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py
index 75e645dff5f..8cc89d90b2f 100644
--- a/homeassistant/components/blink/alarm_control_panel.py
+++ b/homeassistant/components/blink/alarm_control_panel.py
@@ -9,8 +9,6 @@ from . import BLINK_DATA, DEFAULT_ATTRIBUTION
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['blink']
-
ICON = 'mdi:security'
diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py
index 466b73caf5f..4c268989d32 100644
--- a/homeassistant/components/blink/binary_sensor.py
+++ b/homeassistant/components/blink/binary_sensor.py
@@ -4,8 +4,6 @@ from homeassistant.const import CONF_MONITORED_CONDITIONS
from . import BINARY_SENSORS, BLINK_DATA
-DEPENDENCIES = ['blink']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the blink binary sensors."""
diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py
index 1da3080e3ff..d1301319a81 100644
--- a/homeassistant/components/blink/camera.py
+++ b/homeassistant/components/blink/camera.py
@@ -7,8 +7,6 @@ from . import BLINK_DATA, DEFAULT_BRAND
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['blink']
-
ATTR_VIDEO_CLIP = 'video'
ATTR_IMAGE = 'image'
diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json
new file mode 100644
index 00000000000..7be44f95a53
--- /dev/null
+++ b/homeassistant/components/blink/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "blink",
+ "name": "Blink",
+ "documentation": "https://www.home-assistant.io/components/blink",
+ "requirements": [
+ "blinkpy==0.13.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fronzbot"
+ ]
+}
diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py
index 0e97db9d7d4..6fb8be8e4ea 100644
--- a/homeassistant/components/blink/sensor.py
+++ b/homeassistant/components/blink/sensor.py
@@ -8,8 +8,6 @@ from . import BLINK_DATA, SENSORS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['blink']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a Blink sensor."""
diff --git a/homeassistant/components/blinksticklight/light.py b/homeassistant/components/blinksticklight/light.py
index e145005a5a7..8eab6afaeb7 100644
--- a/homeassistant/components/blinksticklight/light.py
+++ b/homeassistant/components/blinksticklight/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Blinkstick lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.blinksticklight/
-"""
+"""Support for Blinkstick lights."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
-REQUIREMENTS = ['blinkstick==1.1.8']
-
_LOGGER = logging.getLogger(__name__)
CONF_SERIAL = 'serial'
diff --git a/homeassistant/components/blinksticklight/manifest.json b/homeassistant/components/blinksticklight/manifest.json
new file mode 100644
index 00000000000..a5277c97d99
--- /dev/null
+++ b/homeassistant/components/blinksticklight/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "blinksticklight",
+ "name": "Blinksticklight",
+ "documentation": "https://www.home-assistant.io/components/blinksticklight",
+ "requirements": [
+ "blinkstick==1.1.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/blinkt/light.py b/homeassistant/components/blinkt/light.py
index 0704881bff9..cb3e854b388 100644
--- a/homeassistant/components/blinkt/light.py
+++ b/homeassistant/components/blinkt/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Blinkt! lights on Raspberry Pi.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.blinkt/
-"""
+"""Support for Blinkt! lights on Raspberry Pi."""
import importlib
import logging
@@ -16,8 +11,6 @@ from homeassistant.components.light import (
from homeassistant.const import CONF_NAME
import homeassistant.util.color as color_util
-REQUIREMENTS = ['blinkt==0.1.0']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_BLINKT = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
diff --git a/homeassistant/components/blinkt/manifest.json b/homeassistant/components/blinkt/manifest.json
new file mode 100644
index 00000000000..c11583ed59e
--- /dev/null
+++ b/homeassistant/components/blinkt/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "blinkt",
+ "name": "Blinkt",
+ "documentation": "https://www.home-assistant.io/components/blinkt",
+ "requirements": [
+ "blinkt==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/blockchain/manifest.json b/homeassistant/components/blockchain/manifest.json
new file mode 100644
index 00000000000..8a2a9f7b71f
--- /dev/null
+++ b/homeassistant/components/blockchain/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "blockchain",
+ "name": "Blockchain",
+ "documentation": "https://www.home-assistant.io/components/blockchain",
+ "requirements": [
+ "python-blockchain-api==0.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py
index 241c98d2328..436e2979a6e 100644
--- a/homeassistant/components/blockchain/sensor.py
+++ b/homeassistant/components/blockchain/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Blockchain.info sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.blockchain/
-"""
+"""Support for Blockchain.info sensors."""
import logging
from datetime import timedelta
@@ -14,8 +9,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-blockchain-api==0.0.2']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by blockchain.info"
diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py
index c8763524de7..b17c4e4c257 100644
--- a/homeassistant/components/bloomsky/binary_sensor.py
+++ b/homeassistant/components/bloomsky/binary_sensor.py
@@ -8,9 +8,9 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import CONF_MONITORED_CONDITIONS
import homeassistant.helpers.config_validation as cv
-_LOGGER = logging.getLogger(__name__)
+from . import BLOOMSKY
-DEPENDENCIES = ['bloomsky']
+_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'Rain': 'moisture',
@@ -25,14 +25,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available BloomSky weather binary sensors."""
- bloomsky = hass.components.bloomsky
# Default needed in case of discovery
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)
- for device in bloomsky.BLOOMSKY.devices.values():
+ for device in BLOOMSKY.devices.values():
for variable in sensors:
add_entities(
- [BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True)
+ [BloomSkySensor(BLOOMSKY, device, variable)], True)
class BloomSkySensor(BinarySensorDevice):
diff --git a/homeassistant/components/bloomsky/camera.py b/homeassistant/components/bloomsky/camera.py
index 5cb2e1adfe1..a748ff2b5b8 100644
--- a/homeassistant/components/bloomsky/camera.py
+++ b/homeassistant/components/bloomsky/camera.py
@@ -5,14 +5,13 @@ import requests
from homeassistant.components.camera import Camera
-DEPENDENCIES = ['bloomsky']
+from . import BLOOMSKY
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up access to BloomSky cameras."""
- bloomsky = hass.components.bloomsky
- for device in bloomsky.BLOOMSKY.devices.values():
- add_entities([BloomSkyCamera(bloomsky.BLOOMSKY, device)])
+ for device in BLOOMSKY.devices.values():
+ add_entities([BloomSkyCamera(BLOOMSKY, device)])
class BloomSkyCamera(Camera):
diff --git a/homeassistant/components/bloomsky/manifest.json b/homeassistant/components/bloomsky/manifest.json
new file mode 100644
index 00000000000..3a780507dd5
--- /dev/null
+++ b/homeassistant/components/bloomsky/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "bloomsky",
+ "name": "Bloomsky",
+ "documentation": "https://www.home-assistant.io/components/bloomsky",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py
index 7e6847f0e7e..e7d4bc5c8eb 100644
--- a/homeassistant/components/bloomsky/sensor.py
+++ b/homeassistant/components/bloomsky/sensor.py
@@ -8,9 +8,9 @@ from homeassistant.const import (TEMP_FAHRENHEIT, CONF_MONITORED_CONDITIONS)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-_LOGGER = logging.getLogger(__name__)
+from . import BLOOMSKY
-DEPENDENCIES = ['bloomsky']
+LOGGER = logging.getLogger(__name__)
# These are the available sensors
SENSOR_TYPES = ['Temperature',
@@ -38,14 +38,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available BloomSky weather sensors."""
- bloomsky = hass.components.bloomsky
# Default needed in case of discovery
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)
- for device in bloomsky.BLOOMSKY.devices.values():
+ for device in BLOOMSKY.devices.values():
for variable in sensors:
add_entities(
- [BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True)
+ [BloomSkySensor(BLOOMSKY, device, variable)], True)
class BloomSkySensor(Entity):
diff --git a/homeassistant/components/bluesound/manifest.json b/homeassistant/components/bluesound/manifest.json
new file mode 100644
index 00000000000..9016502b5d3
--- /dev/null
+++ b/homeassistant/components/bluesound/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "bluesound",
+ "name": "Bluesound",
+ "documentation": "https://www.home-assistant.io/components/bluesound",
+ "requirements": [
+ "xmltodict==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py
index b25916c7f66..080afeea280 100644
--- a/homeassistant/components/bluesound/media_player.py
+++ b/homeassistant/components/bluesound/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Bluesound devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.bluesound/
-"""
+"""Support for Bluesound devices."""
import asyncio
from asyncio.futures import CancelledError
from datetime import timedelta
@@ -34,8 +29,6 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['xmltodict==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MASTER = 'master'
diff --git a/homeassistant/components/bluesound/services.yaml b/homeassistant/components/bluesound/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py
index 825ef04ccc5..d256f56e7fe 100644
--- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py
+++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Tracking for bluetooth low energy devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.bluetooth_le_tracker/
-"""
+"""Tracking for bluetooth low energy devices."""
import logging
from homeassistant.helpers.event import track_point_in_utc_time
@@ -11,12 +6,13 @@ from homeassistant.components.device_tracker import (
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
load_config, SOURCE_TYPE_BLUETOOTH_LE
)
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pygatt[GATTTOOL]==3.2.0']
-
+DATA_BLE = 'BLE'
+DATA_BLE_ADAPTER = 'ADAPTER'
BLE_PREFIX = 'BLE_'
MIN_SEEN_NEW = 5
@@ -26,6 +22,17 @@ def setup_scanner(hass, config, see, discovery_info=None):
# pylint: disable=import-error
import pygatt
new_devices = {}
+ hass.data.setdefault(DATA_BLE, {DATA_BLE_ADAPTER: None})
+
+ def handle_stop(event):
+ """Try to shut down the bluetooth child process nicely."""
+ # These should never be unset at the point this runs, but just for
+ # safety's sake, use `get`.
+ adapter = hass.data.get(DATA_BLE, {}).get(DATA_BLE_ADAPTER)
+ if adapter is not None:
+ adapter.kill()
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop)
def see_device(address, name, new_device=False):
"""Mark a device as seen."""
@@ -55,6 +62,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
_LOGGER.debug("Discovering Bluetooth LE devices")
try:
adapter = pygatt.GATTToolBackend()
+ hass.data[DATA_BLE][DATA_BLE_ADAPTER] = adapter
devs = adapter.scan()
devices = {x['address']: x['name'] for x in devs}
diff --git a/homeassistant/components/bluetooth_le_tracker/manifest.json b/homeassistant/components/bluetooth_le_tracker/manifest.json
new file mode 100644
index 00000000000..d2f8f10290e
--- /dev/null
+++ b/homeassistant/components/bluetooth_le_tracker/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "bluetooth_le_tracker",
+ "name": "Bluetooth le tracker",
+ "documentation": "https://www.home-assistant.io/components/bluetooth_le_tracker",
+ "requirements": [
+ "pygatt[GATTTOOL]==4.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py
index 89f3b95ac1b..d464e87ce64 100644
--- a/homeassistant/components/bluetooth_tracker/device_tracker.py
+++ b/homeassistant/components/bluetooth_tracker/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Tracking for bluetooth devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.bluetooth_tracker/
-"""
+"""Tracking for bluetooth devices."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pybluez==0.22', 'bt_proximity==0.1.2']
-
BT_PREFIX = 'BT_'
CONF_REQUEST_RSSI = 'request_rssi'
diff --git a/homeassistant/components/bluetooth_tracker/manifest.json b/homeassistant/components/bluetooth_tracker/manifest.json
new file mode 100644
index 00000000000..7eaeb4ef927
--- /dev/null
+++ b/homeassistant/components/bluetooth_tracker/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "bluetooth_tracker",
+ "name": "Bluetooth tracker",
+ "documentation": "https://www.home-assistant.io/components/bluetooth_tracker",
+ "requirements": [
+ "bt_proximity==0.1.2",
+ "pybluez==0.22"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bluetooth_tracker/services.yaml b/homeassistant/components/bluetooth_tracker/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/bme280/manifest.json b/homeassistant/components/bme280/manifest.json
new file mode 100644
index 00000000000..2342c8418eb
--- /dev/null
+++ b/homeassistant/components/bme280/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "bme280",
+ "name": "Bme280",
+ "documentation": "https://www.home-assistant.io/components/bme280",
+ "requirements": [
+ "i2csense==0.0.4",
+ "smbus-cffi==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py
index a6b773040ef..66b4ba67258 100644
--- a/homeassistant/components/bme280/sensor.py
+++ b/homeassistant/components/bme280/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for BME280 temperature, humidity and pressure sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.bme280/
-"""
+"""Support for BME280 temperature, humidity and pressure sensor."""
from datetime import timedelta
from functools import partial
import logging
@@ -18,9 +13,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from homeassistant.util.temperature import celsius_to_fahrenheit
-REQUIREMENTS = ['i2csense==0.0.4',
- 'smbus-cffi==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_I2C_ADDRESS = 'i2c_address'
diff --git a/homeassistant/components/bme680/manifest.json b/homeassistant/components/bme680/manifest.json
new file mode 100644
index 00000000000..976be85ca94
--- /dev/null
+++ b/homeassistant/components/bme680/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "bme680",
+ "name": "Bme680",
+ "documentation": "https://www.home-assistant.io/components/bme680",
+ "requirements": [
+ "bme680==1.0.5",
+ "smbus-cffi==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py
index 8d620e459d0..73fe827be6b 100644
--- a/homeassistant/components/bme680/sensor.py
+++ b/homeassistant/components/bme680/sensor.py
@@ -1,12 +1,4 @@
-"""
-Support for BME680 Sensor over SMBus.
-
-Temperature, humidity, pressure and volatile gas support.
-Air Quality calculation based on humidity and volatile gas.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.bme680/
-"""
+"""Support for BME680 Sensor over SMBus."""
import importlib
import logging
@@ -21,9 +13,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util.temperature import celsius_to_fahrenheit
-REQUIREMENTS = ['bme680==1.0.5',
- 'smbus-cffi==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_I2C_ADDRESS = 'i2c_address'
diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py
index e1ac30120d2..10c58696740 100644
--- a/homeassistant/components/bmw_connected_drive/__init__.py
+++ b/homeassistant/components/bmw_connected_drive/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['bimmer_connected==0.5.3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'bmw_connected_drive'
diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py
index deab157292d..8769fcf7d62 100644
--- a/homeassistant/components/bmw_connected_drive/binary_sensor.py
+++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py
@@ -6,8 +6,6 @@ from homeassistant.const import LENGTH_KILOMETERS
from . import DOMAIN as BMW_DOMAIN
-DEPENDENCIES = ['bmw_connected_drive']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py
index 20e84e33e29..229488186ae 100644
--- a/homeassistant/components/bmw_connected_drive/device_tracker.py
+++ b/homeassistant/components/bmw_connected_drive/device_tracker.py
@@ -5,8 +5,6 @@ from homeassistant.util import slugify
from . import DOMAIN as BMW_DOMAIN
-DEPENDENCIES = ['bmw_connected_drive']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py
index fe646dcd1c9..455e1427b05 100644
--- a/homeassistant/components/bmw_connected_drive/lock.py
+++ b/homeassistant/components/bmw_connected_drive/lock.py
@@ -6,8 +6,6 @@ from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
from . import DOMAIN as BMW_DOMAIN
-DEPENDENCIES = ['bmw_connected_drive']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json
new file mode 100644
index 00000000000..67bfac91052
--- /dev/null
+++ b/homeassistant/components/bmw_connected_drive/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "bmw_connected_drive",
+ "name": "Bmw connected drive",
+ "documentation": "https://www.home-assistant.io/components/bmw_connected_drive",
+ "requirements": [
+ "bimmer_connected==0.5.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ChristianKuehnel"
+ ]
+}
diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py
index 03c03f01b4a..4d8b7adde1b 100644
--- a/homeassistant/components/bmw_connected_drive/sensor.py
+++ b/homeassistant/components/bmw_connected_drive/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.helpers.icon import icon_for_battery_level
from . import DOMAIN as BMW_DOMAIN
-DEPENDENCIES = ['bmw_connected_drive']
-
_LOGGER = logging.getLogger(__name__)
ATTR_TO_HA_METRIC = {
diff --git a/homeassistant/components/bom/camera.py b/homeassistant/components/bom/camera.py
new file mode 100644
index 00000000000..87ffd4ab791
--- /dev/null
+++ b/homeassistant/components/bom/camera.py
@@ -0,0 +1,78 @@
+"""Provide animated GIF loops of BOM radar imagery."""
+import voluptuous as vol
+
+from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
+from homeassistant.const import CONF_ID, CONF_NAME
+from homeassistant.helpers import config_validation as cv
+
+CONF_DELTA = 'delta'
+CONF_FRAMES = 'frames'
+CONF_LOCATION = 'location'
+CONF_OUTFILE = 'filename'
+
+LOCATIONS = [
+ 'Adelaide', 'Albany', 'AliceSprings', 'Bairnsdale', 'Bowen', 'Brisbane',
+ 'Broome', 'Cairns', 'Canberra', 'Carnarvon', 'Ceduna', 'Dampier', 'Darwin',
+ 'Emerald', 'Esperance', 'Geraldton', 'Giles', 'Gladstone', 'Gove',
+ 'Grafton', 'Gympie', 'HallsCreek', 'Hobart', 'Kalgoorlie', 'Katherine',
+ 'Learmonth', 'Longreach', 'Mackay', 'Marburg', 'Melbourne', 'Mildura',
+ 'Moree', 'MorningtonIs', 'MountIsa', 'MtGambier', 'Namoi', 'Newcastle',
+ 'Newdegate', 'NorfolkIs', 'NWTasmania', 'Perth', 'PortHedland',
+ 'SellicksHill', 'SouthDoodlakine', 'Sydney', 'Townsville', 'WaggaWagga',
+ 'Warrego', 'Warruwi', 'Watheroo', 'Weipa', 'WillisIs', 'Wollongong',
+ 'Woomera', 'Wyndham', 'Yarrawonga',
+]
+
+
+def _validate_schema(config):
+ if config.get(CONF_LOCATION) is None:
+ if not all(config.get(x) for x in (CONF_ID, CONF_DELTA, CONF_FRAMES)):
+ raise vol.Invalid(
+ "Specify '{}', '{}' and '{}' when '{}' is unspecified".format(
+ CONF_ID, CONF_DELTA, CONF_FRAMES, CONF_LOCATION))
+ return config
+
+
+LOCATIONS_MSG = "Set '{}' to one of: {}".format(
+ CONF_LOCATION, ', '.join(sorted(LOCATIONS)))
+XOR_MSG = "Specify exactly one of '{}' or '{}'".format(CONF_ID, CONF_LOCATION)
+
+PLATFORM_SCHEMA = vol.All(
+ PLATFORM_SCHEMA.extend({
+ vol.Exclusive(CONF_ID, 'xor', msg=XOR_MSG): cv.string,
+ vol.Exclusive(CONF_LOCATION, 'xor', msg=XOR_MSG): vol.In(
+ LOCATIONS, msg=LOCATIONS_MSG),
+ vol.Optional(CONF_DELTA): cv.positive_int,
+ vol.Optional(CONF_FRAMES): cv.positive_int,
+ vol.Optional(CONF_NAME): cv.string,
+ vol.Optional(CONF_OUTFILE): cv.string,
+ }), _validate_schema)
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up BOM radar-loop camera component."""
+ location = config.get(CONF_LOCATION) or "ID {}".format(config.get(CONF_ID))
+ name = config.get(CONF_NAME) or "BOM Radar Loop - {}".format(location)
+ args = [config.get(x) for x in (CONF_LOCATION, CONF_ID, CONF_DELTA,
+ CONF_FRAMES, CONF_OUTFILE)]
+ add_entities([BOMRadarCam(name, *args)])
+
+
+class BOMRadarCam(Camera):
+ """A camera component producing animated BOM radar-imagery GIFs."""
+
+ def __init__(self, name, location, radar_id, delta, frames, outfile):
+ """Initialize the component."""
+ from bomradarloop import BOMRadarLoop
+ super().__init__()
+ self._name = name
+ self._cam = BOMRadarLoop(location, radar_id, delta, frames, outfile)
+
+ def camera_image(self):
+ """Return the current BOM radar-loop image."""
+ return self._cam.current
+
+ @property
+ def name(self):
+ """Return the component name."""
+ return self._name
diff --git a/homeassistant/components/bom/manifest.json b/homeassistant/components/bom/manifest.json
new file mode 100644
index 00000000000..cb7ce4383b0
--- /dev/null
+++ b/homeassistant/components/bom/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "bom",
+ "name": "Bom",
+ "documentation": "https://www.home-assistant.io/components/bom",
+ "requirements": [
+ "bomradarloop==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bom/sensor.py b/homeassistant/components/bom/sensor.py
index 62a3706034a..4c96315ec1f 100644
--- a/homeassistant/components/bom/sensor.py
+++ b/homeassistant/components/bom/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Australian BOM (Bureau of Meteorology) weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.bom/
-"""
+"""Support for Australian BOM (Bureau of Meteorology) weather service."""
import datetime
import ftplib
import gzip
diff --git a/homeassistant/components/braviatv/manifest.json b/homeassistant/components/braviatv/manifest.json
new file mode 100644
index 00000000000..d8a835676b8
--- /dev/null
+++ b/homeassistant/components/braviatv/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "braviatv",
+ "name": "Braviatv",
+ "documentation": "https://www.home-assistant.io/components/braviatv",
+ "requirements": [
+ "braviarc-homeassistant==0.3.7.dev0"
+ ],
+ "dependencies": [
+ "configurator"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py
index 7efb7abd569..6377561009d 100644
--- a/homeassistant/components/braviatv/media_player.py
+++ b/homeassistant/components/braviatv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with a Sony Bravia TV.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.braviatv/
-"""
+"""Support for interface with a Sony Bravia TV."""
import logging
import re
@@ -20,8 +15,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['braviarc-homeassistant==0.3.7.dev0']
-
BRAVIA_CONFIG_FILE = 'bravia.conf'
CLIENTID_PREFIX = 'HomeAssistant'
diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py
index 5055c7fa597..3404bdef99b 100644
--- a/homeassistant/components/broadlink/__init__.py
+++ b/homeassistant/components/broadlink/__init__.py
@@ -1 +1,111 @@
"""The broadlink component."""
+import asyncio
+from base64 import b64decode, b64encode
+import logging
+import re
+import socket
+
+from datetime import timedelta
+import voluptuous as vol
+
+from homeassistant.const import CONF_HOST
+import homeassistant.helpers.config_validation as cv
+from homeassistant.util.dt import utcnow
+
+from .const import CONF_PACKET, DOMAIN, SERVICE_LEARN, SERVICE_SEND
+
+_LOGGER = logging.getLogger(__name__)
+
+DEFAULT_RETRY = 3
+
+
+def ipv4_address(value):
+ """Validate an ipv4 address."""
+ regex = re.compile(r'^\d+\.\d+\.\d+\.\d+$')
+ if not regex.match(value):
+ raise vol.Invalid('Invalid Ipv4 address, expected a.b.c.d')
+ return value
+
+
+def data_packet(value):
+ """Decode a data packet given for broadlink."""
+ return b64decode(cv.string(value))
+
+
+SERVICE_SEND_SCHEMA = vol.Schema({
+ vol.Required(CONF_HOST): ipv4_address,
+ vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet])
+})
+
+SERVICE_LEARN_SCHEMA = vol.Schema({
+ vol.Required(CONF_HOST): ipv4_address,
+})
+
+
+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):
+
+ 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")
+ 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 = "Received packet is: {}".\
+ format(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)
+
+ if not hass.services.has_service(DOMAIN, SERVICE_SEND):
+
+ 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.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)
diff --git a/homeassistant/components/broadlink/const.py b/homeassistant/components/broadlink/const.py
new file mode 100644
index 00000000000..1c4e0ae7948
--- /dev/null
+++ b/homeassistant/components/broadlink/const.py
@@ -0,0 +1,7 @@
+"""Constants for broadlink platform."""
+CONF_PACKET = 'packet'
+
+DOMAIN = 'broadlink'
+
+SERVICE_LEARN = 'learn'
+SERVICE_SEND = 'send'
diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json
new file mode 100644
index 00000000000..a2c565c3dd5
--- /dev/null
+++ b/homeassistant/components/broadlink/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "broadlink",
+ "name": "Broadlink",
+ "documentation": "https://www.home-assistant.io/components/broadlink",
+ "requirements": [
+ "broadlink==0.9.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/broadlink/sensor.py b/homeassistant/components/broadlink/sensor.py
index 5720201b3f2..c542d8f5549 100644
--- a/homeassistant/components/broadlink/sensor.py
+++ b/homeassistant/components/broadlink/sensor.py
@@ -1,26 +1,18 @@
-"""
-Support for the Broadlink RM2 Pro (only temperature) and A1 devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.broadlink/
-"""
-from datetime import timedelta
+"""Support for the Broadlink RM2 Pro (only temperature) and A1 devices."""
import binascii
import logging
import socket
+from datetime import timedelta
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_MAC, CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS,
- CONF_TIMEOUT, CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL,
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
+ CONF_TIMEOUT, CONF_SCAN_INTERVAL)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['broadlink==0.9.0']
_LOGGER = logging.getLogger(__name__)
@@ -36,24 +28,14 @@ SENSOR_TYPES = {
'noise': ['Noise', ' '],
}
-PLATFORM_SCHEMA = vol.All(
- PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str),
- vol.Optional(CONF_MONITORED_CONDITIONS, default=[]):
- vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Required(CONF_HOST): cv.string,
- vol.Required(CONF_MAC): cv.string,
- vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=SCAN_INTERVAL
- )
-)
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str),
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=[]):
+ vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_MAC): cv.string,
+ vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int
+})
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/broadlink/services.yaml b/homeassistant/components/broadlink/services.yaml
new file mode 100644
index 00000000000..2281cb1cc4d
--- /dev/null
+++ b/homeassistant/components/broadlink/services.yaml
@@ -0,0 +1,9 @@
+send:
+ description: Send a raw packet to device.
+ fields:
+ host: {description: IP address of device to send packet via. This must be an already configured device., example: "192.168.0.1"}
+ packet: {description: base64 encoded packet.}
+learn:
+ description: Learn a IR or RF code from remote.
+ fields:
+ host: {description: IP address of device to send packet via. This must be an already configured device., example: "192.168.0.1"}
diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py
index 2237a0a2977..d1b769e3d83 100644
--- a/homeassistant/components/broadlink/switch.py
+++ b/homeassistant/components/broadlink/switch.py
@@ -1,11 +1,4 @@
-"""
-Support for Broadlink RM devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.broadlink/
-"""
-import asyncio
-from base64 import b64decode, b64encode
+"""Support for Broadlink RM devices."""
import binascii
from datetime import timedelta
import logging
@@ -14,15 +7,14 @@ import socket
import voluptuous as vol
from homeassistant.components.switch import (
- DOMAIN, PLATFORM_SCHEMA, SwitchDevice, ENTITY_ID_FORMAT)
+ ENTITY_ID_FORMAT, PLATFORM_SCHEMA, SwitchDevice)
from homeassistant.const import (
CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_FRIENDLY_NAME, CONF_HOST, CONF_MAC,
CONF_SWITCHES, CONF_TIMEOUT, CONF_TYPE)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle, slugify
-from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['broadlink==0.9.0']
+from . import async_setup_service, data_packet
_LOGGER = logging.getLogger(__name__)
@@ -30,9 +22,6 @@ TIME_BETWEEN_UPDATES = timedelta(seconds=5)
DEFAULT_NAME = 'Broadlink switch'
DEFAULT_TIMEOUT = 10
-DEFAULT_RETRY = 3
-SERVICE_LEARN = 'broadlink_learn_command'
-SERVICE_SEND = 'broadlink_send_packet'
CONF_SLOTS = 'slots'
RM_TYPES = ['rm', 'rm2', 'rm_mini', 'rm_pro_phicomm', 'rm2_home_plus',
@@ -45,8 +34,8 @@ MP1_TYPES = ['mp1']
SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES
SWITCH_SCHEMA = vol.Schema({
- vol.Optional(CONF_COMMAND_OFF): cv.string,
- vol.Optional(CONF_COMMAND_ON): cv.string,
+ vol.Optional(CONF_COMMAND_OFF): data_packet,
+ vol.Optional(CONF_COMMAND_ON): data_packet,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
})
@@ -80,57 +69,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
config.get(CONF_MAC).encode().replace(b':', b''))
switch_type = config.get(CONF_TYPE)
- async def _learn_command(call):
- """Handle a learn command."""
- try:
- auth = await hass.async_add_job(broadlink_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_job(broadlink_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_job(
- broadlink_device.check_data)
- if packet:
- log_msg = "Received packet is: {}".\
- format(b64encode(packet).decode('utf8'))
- _LOGGER.info(log_msg)
- hass.components.persistent_notification.async_create(
- log_msg, title='Broadlink switch')
- return
- await asyncio.sleep(1, loop=hass.loop)
- _LOGGER.error("Did not received any signal")
- hass.components.persistent_notification.async_create(
- "Did not received any signal", title='Broadlink switch')
-
- async def _send_packet(call):
- """Send a packet."""
- packets = call.data.get('packet', [])
- for packet in packets:
- for retry in range(DEFAULT_RETRY):
- try:
- extra = len(packet) % 4
- if extra > 0:
- packet = packet + ('=' * (4 - extra))
- payload = b64decode(packet)
- await hass.async_add_job(
- broadlink_device.send_data, payload)
- break
- except (socket.timeout, ValueError):
- try:
- await hass.async_add_job(
- broadlink_device.auth)
- except socket.timeout:
- if retry == DEFAULT_RETRY-1:
- _LOGGER.error("Failed to send packet to device")
-
def _get_mp1_slot_name(switch_friendly_name, slot):
"""Get slot name."""
if not slots['slot_{}'.format(slot)]:
@@ -139,13 +77,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if switch_type in RM_TYPES:
broadlink_device = broadlink.rm((ip_addr, 80), mac_addr, None)
- hass.services.register(DOMAIN, SERVICE_LEARN + '_' +
- slugify(ip_addr.replace('.', '_')),
- _learn_command)
- hass.services.register(DOMAIN, SERVICE_SEND + '_' +
- slugify(ip_addr.replace('.', '_')),
- _send_packet,
- vol.Schema({'packet': cv.ensure_list}))
+ hass.add_job(async_setup_service, hass, ip_addr, broadlink_device)
+
switches = []
for object_id, device_config in devices.items():
switches.append(
@@ -190,8 +123,8 @@ class BroadlinkRMSwitch(SwitchDevice):
self.entity_id = ENTITY_ID_FORMAT.format(slugify(name))
self._name = friendly_name
self._state = False
- self._command_on = b64decode(command_on) if command_on else None
- self._command_off = b64decode(command_off) if command_off else None
+ self._command_on = command_on
+ self._command_off = command_off
self._device = device
@property
diff --git a/homeassistant/components/brottsplatskartan/manifest.json b/homeassistant/components/brottsplatskartan/manifest.json
new file mode 100644
index 00000000000..d3b0657fed8
--- /dev/null
+++ b/homeassistant/components/brottsplatskartan/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "brottsplatskartan",
+ "name": "Brottsplatskartan",
+ "documentation": "https://www.home-assistant.io/components/brottsplatskartan",
+ "requirements": [
+ "brottsplatskartan==0.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py
index c308f2eac53..c36c5c0ad1c 100644
--- a/homeassistant/components/brottsplatskartan/sensor.py
+++ b/homeassistant/components/brottsplatskartan/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor platform for Brottsplatskartan information.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.brottsplatskartan/
-"""
+"""Sensor platform for Brottsplatskartan information."""
from collections import defaultdict
from datetime import timedelta
import logging
@@ -17,8 +12,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['brottsplatskartan==0.0.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_AREA = 'area'
diff --git a/homeassistant/components/browser/manifest.json b/homeassistant/components/browser/manifest.json
new file mode 100644
index 00000000000..61823564fe9
--- /dev/null
+++ b/homeassistant/components/browser/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "browser",
+ "name": "Browser",
+ "documentation": "https://www.home-assistant.io/components/browser",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/browser/services.yaml b/homeassistant/components/browser/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py
index 746f3840a01..f9455ae0910 100644
--- a/homeassistant/components/brunt/cover.py
+++ b/homeassistant/components/brunt/cover.py
@@ -1,9 +1,4 @@
-"""
-Support for Brunt Blind Engine covers.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/cover.brunt
-"""
+"""Support for Brunt Blind Engine covers."""
import logging
@@ -18,8 +13,6 @@ from homeassistant.components.cover import (
)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['brunt==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
diff --git a/homeassistant/components/brunt/manifest.json b/homeassistant/components/brunt/manifest.json
new file mode 100644
index 00000000000..a47e3f69d5c
--- /dev/null
+++ b/homeassistant/components/brunt/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "brunt",
+ "name": "Brunt",
+ "documentation": "https://www.home-assistant.io/components/brunt",
+ "requirements": [
+ "brunt==0.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@eavanvalkenburg"
+ ]
+}
diff --git a/homeassistant/components/bt_home_hub_5/device_tracker.py b/homeassistant/components/bt_home_hub_5/device_tracker.py
index 21c41df3a1d..65f88e05d1c 100644
--- a/homeassistant/components/bt_home_hub_5/device_tracker.py
+++ b/homeassistant/components/bt_home_hub_5/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for BT Home Hub 5.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.bt_home_hub_5/
-"""
+"""Support for BT Home Hub 5."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA,
DeviceScanner)
from homeassistant.const import CONF_HOST
-REQUIREMENTS = ['bthomehub5-devicelist==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_IP = '192.168.1.254'
diff --git a/homeassistant/components/bt_home_hub_5/manifest.json b/homeassistant/components/bt_home_hub_5/manifest.json
new file mode 100644
index 00000000000..927d9ea9412
--- /dev/null
+++ b/homeassistant/components/bt_home_hub_5/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "bt_home_hub_5",
+ "name": "Bt home hub 5",
+ "documentation": "https://www.home-assistant.io/components/bt_home_hub_5",
+ "requirements": [
+ "bthomehub5-devicelist==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/bt_smarthub/device_tracker.py b/homeassistant/components/bt_smarthub/device_tracker.py
index 821182ec103..adc873f56b3 100644
--- a/homeassistant/components/bt_smarthub/device_tracker.py
+++ b/homeassistant/components/bt_smarthub/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for BT Smart Hub (Sometimes referred to as BT Home Hub 6).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.bt_smarthub/
-"""
+"""Support for BT Smart Hub (Sometimes referred to as BT Home Hub 6)."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
-REQUIREMENTS = ['btsmarthub_devicelist==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_IP = '192.168.1.254'
diff --git a/homeassistant/components/bt_smarthub/manifest.json b/homeassistant/components/bt_smarthub/manifest.json
new file mode 100644
index 00000000000..725541082e7
--- /dev/null
+++ b/homeassistant/components/bt_smarthub/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "bt_smarthub",
+ "name": "Bt smarthub",
+ "documentation": "https://www.home-assistant.io/components/bt_smarthub",
+ "requirements": [
+ "btsmarthub_devicelist==0.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@jxwolstenholme"
+ ]
+}
diff --git a/homeassistant/components/buienradar/manifest.json b/homeassistant/components/buienradar/manifest.json
new file mode 100644
index 00000000000..98fc5fbdeac
--- /dev/null
+++ b/homeassistant/components/buienradar/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "buienradar",
+ "name": "Buienradar",
+ "documentation": "https://www.home-assistant.io/components/buienradar",
+ "requirements": [
+ "buienradar==0.91"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py
index d144d84cbf8..f3aaa9b7537 100644
--- a/homeassistant/components/buienradar/sensor.py
+++ b/homeassistant/components/buienradar/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Buienradar.nl weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.buienradar/
-"""
+"""Support for Buienradar.nl weather service."""
import asyncio
from datetime import datetime, timedelta
import logging
@@ -22,8 +17,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util
-REQUIREMENTS = ['buienradar==0.91']
-
_LOGGER = logging.getLogger(__name__)
MEASURED_LABEL = 'Measured'
diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py
index 86dcb229a78..7d77bec7cca 100644
--- a/homeassistant/components/buienradar/weather.py
+++ b/homeassistant/components/buienradar/weather.py
@@ -13,8 +13,6 @@ from homeassistant.helpers import config_validation as cv
# Reuse data and API logic from the sensor implementation
from .sensor import BrData
-REQUIREMENTS = ['buienradar==0.91']
-
_LOGGER = logging.getLogger(__name__)
DATA_CONDITION = 'buienradar_condition'
diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py
index cb8874a817c..446473c7f40 100644
--- a/homeassistant/components/caldav/calendar.py
+++ b/homeassistant/components/caldav/calendar.py
@@ -1,9 +1,4 @@
-"""
-Support for WebDav Calendar.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/calendar.caldav/
-"""
+"""Support for WebDav Calendar."""
from datetime import datetime, timedelta
import logging
import re
@@ -17,8 +12,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle, dt
-REQUIREMENTS = ['caldav==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEVICE_ID = 'device_id'
diff --git a/homeassistant/components/caldav/manifest.json b/homeassistant/components/caldav/manifest.json
new file mode 100644
index 00000000000..55cd555d989
--- /dev/null
+++ b/homeassistant/components/caldav/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "caldav",
+ "name": "Caldav",
+ "documentation": "https://www.home-assistant.io/components/caldav",
+ "requirements": [
+ "caldav==0.6.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py
index aa9e3153fe5..73a779816a3 100644
--- a/homeassistant/components/calendar/__init__.py
+++ b/homeassistant/components/calendar/__init__.py
@@ -22,8 +22,6 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'calendar'
-DEPENDENCIES = ['http']
-
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=60)
diff --git a/homeassistant/components/calendar/manifest.json b/homeassistant/components/calendar/manifest.json
new file mode 100644
index 00000000000..3a09cd090a5
--- /dev/null
+++ b/homeassistant/components/calendar/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "calendar",
+ "name": "Calendar",
+ "documentation": "https://www.home-assistant.io/components/calendar",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py
index 9ec081166bb..7a37dffe3b8 100644
--- a/homeassistant/components/camera/__init__.py
+++ b/homeassistant/components/camera/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to interface with cameras.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/camera/
-"""
+"""Component to interface with cameras."""
import asyncio
import base64
import collections
@@ -20,7 +15,7 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
- SERVICE_TURN_ON, EVENT_HOMEASSISTANT_START, CONF_FILENAME
+ SERVICE_TURN_ON, CONF_FILENAME
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import Entity
@@ -37,12 +32,11 @@ from homeassistant.components.stream.const import (
CONF_DURATION, SERVICE_RECORD, DOMAIN as DOMAIN_STREAM)
from homeassistant.components import websocket_api
import homeassistant.helpers.config_validation as cv
+from homeassistant.setup import async_when_setup
from .const import DOMAIN, DATA_CAMERA_PREFS
from .prefs import CameraPreferences
-DEPENDENCIES = ['http']
-
_LOGGER = logging.getLogger(__name__)
SERVICE_ENABLE_MOTION = 'enable_motion_detection'
@@ -224,14 +218,13 @@ async def async_setup(hass, config):
await component.async_setup(config)
- @callback
- def preload_stream(event):
+ async def preload_stream(hass, _):
for camera in component.entities:
camera_prefs = prefs.get(camera.entity_id)
if camera.stream_source and camera_prefs.preload_stream:
request_stream(hass, camera.stream_source, keepalive=True)
- hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream)
+ async_when_setup(hass, DOMAIN_STREAM, preload_stream)
@callback
def update_tokens(time):
diff --git a/homeassistant/components/camera/manifest.json b/homeassistant/components/camera/manifest.json
new file mode 100644
index 00000000000..3af6a15ca52
--- /dev/null
+++ b/homeassistant/components/camera/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "camera",
+ "name": "Camera",
+ "documentation": "https://www.home-assistant.io/components/camera",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "after_dependencies": [
+ "stream"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml
index a3e42300cbd..4c2d89db86d 100644
--- a/homeassistant/components/camera/services.yaml
+++ b/homeassistant/components/camera/services.yaml
@@ -93,39 +93,3 @@ onvif_ptz:
zoom:
description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT"
example: "ZOOM_IN"
-
-logi_circle_set_config:
- description: Set a configuration property.
- fields:
- entity_id:
- description: Name(s) of entities to apply the operation mode to.
- example: "camera.living_room_camera"
- mode:
- description: "Operation mode. Allowed values: BATTERY_SAVING, LED, PRIVACY_MODE."
- example: "PRIVACY_MODE"
- value:
- description: "Operation value. Allowed values: true, false"
- example: true
-
-logi_circle_livestream_snapshot:
- description: Take a snapshot from the camera's livestream. Will wake the camera from sleep if required.
- fields:
- entity_id:
- description: Name(s) of entities to create snapshots from.
- example: "camera.living_room_camera"
- filename:
- description: Template of a Filename. Variable is entity_id.
- example: "/tmp/snapshot_{{ entity_id }}.jpg"
-
-logi_circle_livestream_record:
- description: Take a video recording from the camera's livestream.
- fields:
- entity_id:
- description: Name(s) of entities to create recordings from.
- example: "camera.living_room_camera"
- filename:
- description: Template of a Filename. Variable is entity_id.
- example: "/tmp/snapshot_{{ entity_id }}.mp4"
- duration:
- description: Recording duration in seconds.
- example: 60
diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py
index e53c7e22d2d..52b38f14795 100644
--- a/homeassistant/components/canary/__init__.py
+++ b/homeassistant/components/canary/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
-REQUIREMENTS = ['py-canary==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
NOTIFICATION_ID = 'canary_notification'
diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py
index 61794224666..7402d785532 100644
--- a/homeassistant/components/canary/alarm_control_panel.py
+++ b/homeassistant/components/canary/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for Canary alarm.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.canary/
-"""
+"""Support for Canary alarm."""
import logging
from homeassistant.components.alarm_control_panel import AlarmControlPanel
@@ -13,8 +8,6 @@ from homeassistant.const import (
from . import DATA_CANARY
-DEPENDENCIES = ['canary']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py
index c3a3af32450..33e1265921f 100644
--- a/homeassistant/components/canary/camera.py
+++ b/homeassistant/components/canary/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for Canary camera.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.canary/
-"""
+"""Support for Canary camera."""
import asyncio
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ from homeassistant.util import Throttle
from . import DATA_CANARY, DEFAULT_TIMEOUT
-DEPENDENCIES = ['canary', 'ffmpeg']
-
_LOGGER = logging.getLogger(__name__)
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
diff --git a/homeassistant/components/canary/manifest.json b/homeassistant/components/canary/manifest.json
new file mode 100644
index 00000000000..346c1c99f6d
--- /dev/null
+++ b/homeassistant/components/canary/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "canary",
+ "name": "Canary",
+ "documentation": "https://www.home-assistant.io/components/canary",
+ "requirements": [
+ "py-canary==0.5.0"
+ ],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py
index d24c00c9266..220abc9b387 100644
--- a/homeassistant/components/canary/sensor.py
+++ b/homeassistant/components/canary/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Canary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.canary/
-"""
+"""Support for Canary sensors."""
from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.entity import Entity
@@ -11,8 +6,6 @@ from homeassistant.helpers.icon import icon_for_battery_level
from . import DATA_CANARY
-DEPENDENCIES = ['canary']
-
SENSOR_VALUE_PRECISION = 2
ATTR_AIR_QUALITY = "air_quality"
diff --git a/homeassistant/components/cast/.translations/ko.json b/homeassistant/components/cast/.translations/ko.json
index e4472c88cd8..32c744c8f20 100644
--- a/homeassistant/components/cast/.translations/ko.json
+++ b/homeassistant/components/cast/.translations/ko.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "no_devices_found": "Googgle Cast \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
+ "no_devices_found": "Googgle Cast \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "\ud558\ub098\uc758 Google Cast \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"step": {
diff --git a/homeassistant/components/cast/.translations/th.json b/homeassistant/components/cast/.translations/th.json
index f0b06a06dc9..372a9cf0760 100644
--- a/homeassistant/components/cast/.translations/th.json
+++ b/homeassistant/components/cast/.translations/th.json
@@ -1,9 +1,14 @@
{
"config": {
+ "abort": {
+ "no_devices_found": "\u0e44\u0e21\u0e48\u0e1e\u0e1a\u0e2d\u0e38\u0e1b\u0e01\u0e23\u0e13\u0e4c Google Cast \u0e1a\u0e19\u0e40\u0e04\u0e23\u0e37\u0e2d\u0e02\u0e48\u0e32\u0e22"
+ },
"step": {
"confirm": {
- "description": "\u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Google Cast \u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?"
+ "description": "\u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Google Cast \u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?",
+ "title": "Google Cast"
}
- }
+ },
+ "title": "Google Cast"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py
index bc32b36c455..1a93020c229 100644
--- a/homeassistant/components/cast/__init__.py
+++ b/homeassistant/components/cast/__init__.py
@@ -2,8 +2,6 @@
from homeassistant import config_entries
from homeassistant.helpers import config_entry_flow
-REQUIREMENTS = ['pychromecast==3.0.0']
-
DOMAIN = 'cast'
diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json
new file mode 100644
index 00000000000..c506dba8cf1
--- /dev/null
+++ b/homeassistant/components/cast/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "cast",
+ "name": "Cast",
+ "documentation": "https://www.home-assistant.io/components/cast",
+ "requirements": [
+ "pychromecast==3.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py
index 77332883a91..4b2972b0c00 100644
--- a/homeassistant/components/cast/media_player.py
+++ b/homeassistant/components/cast/media_player.py
@@ -12,8 +12,8 @@ from homeassistant.components.media_player import (
from homeassistant.components.media_player.const import (
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE,
- SUPPORT_VOLUME_SET)
+ SUPPORT_SEEK, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
+ SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
from homeassistant.const import (
CONF_HOST, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_OFF, STATE_PAUSED,
STATE_PLAYING)
@@ -36,9 +36,9 @@ CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
DEFAULT_PORT = 8009
-SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
- SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
- SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_PLAY
+SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA | \
+ SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \
+ SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET
# Stores a threading.Lock that is held by the internal pychromecast discovery.
INTERNAL_DISCOVERY_RUNNING_KEY = 'cast_discovery_running'
@@ -48,6 +48,8 @@ KNOWN_CHROMECAST_INFO_KEY = 'cast_known_chromecasts'
# Stores UUIDs of cast devices that were added as entities. Doesn't store
# None UUIDs.
ADDED_CAST_DEVICES_KEY = 'cast_added_cast_devices'
+# Stores an audio group manager.
+CAST_MULTIZONE_MANAGER_KEY = 'cast_multizone_manager'
# Dispatcher signal fired with a ChromecastInfo every time we discover a new
# Chromecast or receive it through configuration
@@ -79,6 +81,7 @@ class ChromecastInfo:
manufacturer = attr.ib(type=str, default='')
model_name = attr.ib(type=str, default='')
friendly_name = attr.ib(type=Optional[str], default=None)
+ is_dynamic_group = attr.ib(type=Optional[bool], default=None)
@property
def is_audio_group(self) -> bool:
@@ -88,7 +91,13 @@ class ChromecastInfo:
@property
def is_information_complete(self) -> bool:
"""Return if all information is filled out."""
- return all(attr.astuple(self))
+ want_dynamic_group = self.is_audio_group
+ have_dynamic_group = self.is_dynamic_group is not None
+ have_all_except_dynamic_group = all(
+ attr.astuple(self, filter=attr.filters.exclude(
+ attr.fields(ChromecastInfo).is_dynamic_group)))
+ return (have_all_except_dynamic_group and
+ (not want_dynamic_group or have_dynamic_group))
@property
def host_port(self) -> Tuple[str, int]:
@@ -96,9 +105,16 @@ class ChromecastInfo:
return self.host, self.port
+def _is_matching_dynamic_group(our_info: ChromecastInfo,
+ new_info: ChromecastInfo,) -> bool:
+ return (our_info.is_audio_group and
+ new_info.is_dynamic_group and
+ our_info.friendly_name == new_info.friendly_name)
+
+
def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo:
"""Fill out missing attributes of ChromecastInfo using blocking HTTP."""
- if info.is_information_complete or info.is_audio_group:
+ if info.is_information_complete:
# We have all information, no need to check HTTP API. Or this is an
# audio group, so checking via HTTP won't give us any new information.
return info
@@ -106,6 +122,28 @@ def _fill_out_missing_chromecast_info(info: ChromecastInfo) -> ChromecastInfo:
# Fill out missing information via HTTP dial.
from pychromecast import dial
+ if info.is_audio_group:
+ is_dynamic_group = False
+ http_group_status = None
+ dynamic_groups = []
+ if info.uuid:
+ http_group_status = dial.get_multizone_status(
+ info.host, services=[info.service],
+ zconf=ChromeCastZeroconf.get_zeroconf())
+ if http_group_status is not None:
+ dynamic_groups = \
+ [str(g.uuid) for g in http_group_status.dynamic_groups]
+ is_dynamic_group = info.uuid in dynamic_groups
+
+ return ChromecastInfo(
+ service=info.service, host=info.host, port=info.port,
+ uuid=info.uuid,
+ friendly_name=info.friendly_name,
+ manufacturer=info.manufacturer,
+ model_name=info.model_name,
+ is_dynamic_group=is_dynamic_group
+ )
+
http_device_status = dial.get_device_status(
info.host, services=[info.service],
zconf=ChromeCastZeroconf.get_zeroconf())
@@ -218,12 +256,17 @@ def _async_create_cast_device(hass: HomeAssistantType,
Returns None if the cast device has already been added.
"""
+ _LOGGER.debug("_async_create_cast_device: %s", info)
if info.uuid is None:
# Found a cast without UUID, we don't store it because we won't be able
# to update it anyway.
return CastDevice(info)
# Found a cast with UUID
+ if info.is_dynamic_group:
+ # This is a dynamic group, do not add it.
+ return None
+
added_casts = hass.data[ADDED_CAST_DEVICES_KEY]
if info.uuid in added_casts:
# Already added this one, the entity will take care of moved hosts
@@ -322,15 +365,22 @@ class CastStatusListener:
potentially arrive. This class allows invalidating past chromecast objects.
"""
- def __init__(self, cast_device, chromecast):
+ def __init__(self, cast_device, chromecast, mz_mgr):
"""Initialize the status listener."""
self._cast_device = cast_device
+ self._uuid = chromecast.uuid
self._valid = True
+ self._mz_mgr = mz_mgr
chromecast.register_status_listener(self)
chromecast.socket_client.media_controller.register_status_listener(
self)
chromecast.register_connection_listener(self)
+ # pylint: disable=protected-access
+ if cast_device._cast_info.is_audio_group:
+ self._mz_mgr.add_multizone(chromecast)
+ else:
+ self._mz_mgr.register_listener(chromecast.uuid, self)
def new_cast_status(self, cast_status):
"""Handle reception of a new CastStatus."""
@@ -347,11 +397,81 @@ class CastStatusListener:
if self._valid:
self._cast_device.new_connection_status(connection_status)
+ @staticmethod
+ def added_to_multizone(group_uuid):
+ """Handle the cast added to a group."""
+ pass
+
+ def removed_from_multizone(self, group_uuid):
+ """Handle the cast removed from a group."""
+ if self._valid:
+ self._cast_device.multizone_new_media_status(group_uuid, None)
+
+ def multizone_new_cast_status(self, group_uuid, cast_status):
+ """Handle reception of a new CastStatus for a group."""
+ pass
+
+ def multizone_new_media_status(self, group_uuid, media_status):
+ """Handle reception of a new MediaStatus for a group."""
+ if self._valid:
+ self._cast_device.multizone_new_media_status(
+ group_uuid, media_status)
+
def invalidate(self):
"""Invalidate this status listener.
All following callbacks won't be forwarded.
"""
+ # pylint: disable=protected-access
+ if self._cast_device._cast_info.is_audio_group:
+ self._mz_mgr.remove_multizone(self._uuid)
+ else:
+ self._mz_mgr.deregister_listener(self._uuid, self)
+ self._valid = False
+
+
+class DynamicGroupCastStatusListener:
+ """Helper class to handle pychromecast status callbacks.
+
+ Necessary because a CastDevice entity can create a new socket client
+ and therefore callbacks from multiple chromecast connections can
+ potentially arrive. This class allows invalidating past chromecast objects.
+ """
+
+ def __init__(self, cast_device, chromecast, mz_mgr):
+ """Initialize the status listener."""
+ self._cast_device = cast_device
+ self._uuid = chromecast.uuid
+ self._valid = True
+ self._mz_mgr = mz_mgr
+
+ chromecast.register_status_listener(self)
+ chromecast.socket_client.media_controller.register_status_listener(
+ self)
+ chromecast.register_connection_listener(self)
+ self._mz_mgr.add_multizone(chromecast)
+
+ def new_cast_status(self, cast_status):
+ """Handle reception of a new CastStatus."""
+ pass
+
+ def new_media_status(self, media_status):
+ """Handle reception of a new MediaStatus."""
+ if self._valid:
+ self._cast_device.new_dynamic_group_media_status(media_status)
+
+ def new_connection_status(self, connection_status):
+ """Handle reception of a new ConnectionStatus."""
+ if self._valid:
+ self._cast_device.new_dynamic_group_connection_status(
+ connection_status)
+
+ def invalidate(self):
+ """Invalidate this status listener.
+
+ All following callbacks won't be forwarded.
+ """
+ self._mz_mgr.remove_multizone(self._uuid)
self._valid = False
@@ -375,8 +495,19 @@ class CastDevice(MediaPlayerDevice):
self.cast_status = None
self.media_status = None
self.media_status_received = None
+ self._dynamic_group_cast_info = None # type: ChromecastInfo
+ self._dynamic_group_cast = None \
+ # type: Optional[pychromecast.Chromecast]
+ self.dynamic_group_media_status = None
+ self.dynamic_group_media_status_received = None
+ self.mz_media_status = {}
+ self.mz_media_status_received = {}
+ self.mz_mgr = None
self._available = False # type: bool
+ self._dynamic_group_available = False # type: bool
self._status_listener = None # type: Optional[CastStatusListener]
+ self._dynamic_group_status_listener = None \
+ # type: Optional[DynamicGroupCastStatusListener]
self._add_remove_handler = None
self._del_remove_handler = None
@@ -388,6 +519,13 @@ class CastDevice(MediaPlayerDevice):
if self._cast_info.uuid is None:
# We can't handle empty UUIDs
return
+ if _is_matching_dynamic_group(self._cast_info, discover):
+ _LOGGER.debug("Discovered matching dynamic group: %s",
+ discover)
+ self.hass.async_create_task(
+ self.async_set_dynamic_group(discover))
+ return
+
if self._cast_info.uuid != discover.uuid:
# Discovered is not our device.
return
@@ -405,6 +543,11 @@ class CastDevice(MediaPlayerDevice):
if self._cast_info.uuid is None:
# We can't handle empty UUIDs
return
+ if (self._dynamic_group_cast_info is not None and
+ self._dynamic_group_cast_info.uuid == discover.uuid):
+ _LOGGER.debug("Removed matching dynamic group: %s", discover)
+ self.hass.async_create_task(self.async_del_dynamic_group())
+ return
if self._cast_info.uuid != discover.uuid:
# Removed is not our device.
return
@@ -423,6 +566,14 @@ class CastDevice(MediaPlayerDevice):
async_cast_removed)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop)
self.hass.async_create_task(self.async_set_cast_info(self._cast_info))
+ for info in self.hass.data[KNOWN_CHROMECAST_INFO_KEY]:
+ if _is_matching_dynamic_group(self._cast_info, info):
+ _LOGGER.debug("[%s %s (%s:%s)] Found dynamic group: %s",
+ self.entity_id, self._cast_info.friendly_name,
+ self._cast_info.host, self._cast_info.port, info)
+ self.hass.async_create_task(
+ self.async_set_dynamic_group(info))
+ break
async def async_will_remove_from_hass(self) -> None:
"""Disconnect Chromecast object when removed."""
@@ -478,7 +629,14 @@ class CastDevice(MediaPlayerDevice):
cast_info.friendly_name
))
self._chromecast = chromecast
- self._status_listener = CastStatusListener(self, chromecast)
+
+ if CAST_MULTIZONE_MANAGER_KEY not in self.hass.data:
+ from pychromecast.controllers.multizone import MultizoneManager
+ self.hass.data[CAST_MULTIZONE_MANAGER_KEY] = MultizoneManager()
+ self.mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY]
+
+ self._status_listener = CastStatusListener(
+ self, chromecast, self.mz_mgr)
self._available = False
self.cast_status = chromecast.status
self.media_status = chromecast.media_controller.status
@@ -493,6 +651,56 @@ class CastDevice(MediaPlayerDevice):
self._cast_info.host, self._cast_info.port,
cast_info.service, self.services)
+ async def async_set_dynamic_group(self, cast_info):
+ """Set the cast information and set up the chromecast object."""
+ import pychromecast
+ _LOGGER.debug(
+ "[%s %s (%s:%s)] Connecting to dynamic group by host %s",
+ self.entity_id, self._cast_info.friendly_name,
+ self._cast_info.host, self._cast_info.port, cast_info)
+
+ self.async_del_dynamic_group()
+ self._dynamic_group_cast_info = cast_info
+
+ # pylint: disable=protected-access
+ chromecast = await self.hass.async_add_executor_job(
+ pychromecast._get_chromecast_from_host, (
+ cast_info.host, cast_info.port, cast_info.uuid,
+ cast_info.model_name, cast_info.friendly_name
+ ))
+
+ self._dynamic_group_cast = chromecast
+
+ if CAST_MULTIZONE_MANAGER_KEY not in self.hass.data:
+ from pychromecast.controllers.multizone import MultizoneManager
+ self.hass.data[CAST_MULTIZONE_MANAGER_KEY] = MultizoneManager()
+ mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY]
+
+ self._dynamic_group_status_listener = DynamicGroupCastStatusListener(
+ self, chromecast, mz_mgr)
+ self._dynamic_group_available = False
+ self.dynamic_group_media_status = chromecast.media_controller.status
+ self._dynamic_group_cast.start()
+ self.async_schedule_update_ha_state()
+
+ async def async_del_dynamic_group(self):
+ """Remove the dynamic group."""
+ cast_info = self._dynamic_group_cast_info
+ _LOGGER.debug("[%s %s (%s:%s)] Remove dynamic group: %s",
+ self.entity_id, self._cast_info.friendly_name,
+ self._cast_info.host, self._cast_info.port,
+ cast_info.service if cast_info else None)
+
+ self._dynamic_group_available = False
+ self._dynamic_group_cast_info = None
+ if self._dynamic_group_cast is not None:
+ await self.hass.async_add_executor_job(
+ self._dynamic_group_cast.disconnect)
+
+ self._dynamic_group_invalidate()
+
+ self.async_schedule_update_ha_state()
+
async def _async_disconnect(self):
"""Disconnect Chromecast object if it is set."""
if self._chromecast is None:
@@ -504,7 +712,10 @@ class CastDevice(MediaPlayerDevice):
self._available = False
self.async_schedule_update_ha_state()
- await self.hass.async_add_job(self._chromecast.disconnect)
+ await self.hass.async_add_executor_job(self._chromecast.disconnect)
+ if self._dynamic_group_cast is not None:
+ await self.hass.async_add_executor_job(
+ self._dynamic_group_cast.disconnect)
self._invalidate()
@@ -516,10 +727,22 @@ class CastDevice(MediaPlayerDevice):
self.cast_status = None
self.media_status = None
self.media_status_received = None
+ self.mz_media_status = {}
+ self.mz_media_status_received = {}
+ self.mz_mgr = None
if self._status_listener is not None:
self._status_listener.invalidate()
self._status_listener = None
+ def _dynamic_group_invalidate(self):
+ """Invalidate some attributes."""
+ self._dynamic_group_cast = None
+ self.dynamic_group_media_status = None
+ self.dynamic_group_media_status_received = None
+ if self._dynamic_group_status_listener is not None:
+ self._dynamic_group_status_listener.invalidate()
+ self._dynamic_group_status_listener = None
+
# ========== Callbacks ==========
def new_cast_status(self, cast_status):
"""Handle updates of the cast status."""
@@ -565,7 +788,79 @@ class CastDevice(MediaPlayerDevice):
self._available = new_available
self.schedule_update_ha_state()
+ def new_dynamic_group_media_status(self, media_status):
+ """Handle updates of the media status."""
+ self.dynamic_group_media_status = media_status
+ self.dynamic_group_media_status_received = dt_util.utcnow()
+ self.schedule_update_ha_state()
+
+ def new_dynamic_group_connection_status(self, connection_status):
+ """Handle updates of connection status."""
+ from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, \
+ CONNECTION_STATUS_DISCONNECTED
+
+ _LOGGER.debug(
+ "[%s %s (%s:%s)] Received dynamic group connection status: %s",
+ self.entity_id, self._cast_info.friendly_name,
+ self._cast_info.host, self._cast_info.port,
+ connection_status.status)
+ if connection_status.status == CONNECTION_STATUS_DISCONNECTED:
+ self._dynamic_group_available = False
+ self._dynamic_group_invalidate()
+ self.schedule_update_ha_state()
+ return
+
+ new_available = connection_status.status == CONNECTION_STATUS_CONNECTED
+ if new_available != self._dynamic_group_available:
+ # Connection status callbacks happen often when disconnected.
+ # Only update state when availability changed to put less pressure
+ # on state machine.
+ _LOGGER.debug(
+ "[%s %s (%s:%s)] Dynamic group availability changed: %s",
+ self.entity_id, self._cast_info.friendly_name,
+ self._cast_info.host, self._cast_info.port,
+ connection_status.status)
+ self._dynamic_group_available = new_available
+ self.schedule_update_ha_state()
+
+ def multizone_new_media_status(self, group_uuid, media_status):
+ """Handle updates of audio group media status."""
+ _LOGGER.debug(
+ "[%s %s (%s:%s)] Multizone %s media status: %s",
+ self.entity_id, self._cast_info.friendly_name,
+ self._cast_info.host, self._cast_info.port,
+ group_uuid, media_status)
+ self.mz_media_status[group_uuid] = media_status
+ self.mz_media_status_received[group_uuid] = dt_util.utcnow()
+ self.schedule_update_ha_state()
+
# ========== Service Calls ==========
+ def _media_controller(self):
+ """
+ Return media status.
+
+ First try from our own cast, then dynamic groups and finally
+ groups which our cast is a member in.
+ """
+ media_status = self.media_status
+ media_controller = self._chromecast.media_controller
+
+ if ((media_status is None or media_status.player_state == "UNKNOWN")
+ and self._dynamic_group_cast is not None):
+ media_status = self.dynamic_group_media_status
+ media_controller = \
+ self._dynamic_group_cast.media_controller
+
+ if media_status is None or media_status.player_state == "UNKNOWN":
+ groups = self.mz_media_status
+ for k, val in groups.items():
+ if val and val.player_state != "UNKNOWN":
+ media_controller = \
+ self.mz_mgr.get_multizone_mediacontroller(k)
+ break
+
+ return media_controller
+
def turn_on(self):
"""Turn on the cast device."""
import pychromecast
@@ -596,30 +891,37 @@ class CastDevice(MediaPlayerDevice):
def media_play(self):
"""Send play command."""
- self._chromecast.media_controller.play()
+ media_controller = self._media_controller()
+ media_controller.play()
def media_pause(self):
"""Send pause command."""
- self._chromecast.media_controller.pause()
+ media_controller = self._media_controller()
+ media_controller.pause()
def media_stop(self):
"""Send stop command."""
- self._chromecast.media_controller.stop()
+ media_controller = self._media_controller()
+ media_controller.stop()
def media_previous_track(self):
"""Send previous track command."""
- self._chromecast.media_controller.rewind()
+ media_controller = self._media_controller()
+ media_controller.queue_prev()
def media_next_track(self):
"""Send next track command."""
- self._chromecast.media_controller.skip()
+ media_controller = self._media_controller()
+ media_controller.queue_next()
def media_seek(self, position):
"""Seek the media to a specific location."""
- self._chromecast.media_controller.seek(position)
+ media_controller = self._media_controller()
+ media_controller.seek(position)
def play_media(self, media_type, media_id, **kwargs):
"""Play media from a URL."""
+ # We do not want this to be forwarded to a group / dynamic group
self._chromecast.media_controller.play_media(media_id, media_type)
# ========== Properties ==========
@@ -650,16 +952,43 @@ class CastDevice(MediaPlayerDevice):
'manufacturer': cast_info.manufacturer,
}
+ def _media_status(self):
+ """
+ Return media status.
+
+ First try from our own cast, then dynamic groups and finally
+ groups which our cast is a member in.
+ """
+ media_status = self.media_status
+ media_status_received = self.media_status_received
+
+ if ((media_status is None or media_status.player_state == "UNKNOWN")
+ and self._dynamic_group_cast is not None):
+ media_status = self.dynamic_group_media_status
+ media_status_received = self.dynamic_group_media_status_received
+
+ if media_status is None or media_status.player_state == "UNKNOWN":
+ groups = self.mz_media_status
+ for k, val in groups.items():
+ if val and val.player_state != "UNKNOWN":
+ media_status = val
+ media_status_received = self.mz_media_status_received[k]
+ break
+
+ return (media_status, media_status_received)
+
@property
def state(self):
"""Return the state of the player."""
- if self.media_status is None:
+ media_status, _ = self._media_status()
+
+ if media_status is None:
return None
- if self.media_status.player_is_playing:
+ if media_status.player_is_playing:
return STATE_PLAYING
- if self.media_status.player_is_paused:
+ if media_status.player_is_paused:
return STATE_PAUSED
- if self.media_status.player_is_idle:
+ if media_status.player_is_idle:
return STATE_IDLE
if self._chromecast is not None and self._chromecast.is_idle:
return STATE_OFF
@@ -683,75 +1012,87 @@ class CastDevice(MediaPlayerDevice):
@property
def media_content_id(self):
"""Content ID of current playing media."""
- return self.media_status.content_id if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.content_id if media_status else None
@property
def media_content_type(self):
"""Content type of current playing media."""
- if self.media_status is None:
+ media_status, _ = self._media_status()
+ if media_status is None:
return None
- if self.media_status.media_is_tvshow:
+ if media_status.media_is_tvshow:
return MEDIA_TYPE_TVSHOW
- if self.media_status.media_is_movie:
+ if media_status.media_is_movie:
return MEDIA_TYPE_MOVIE
- if self.media_status.media_is_musictrack:
+ if media_status.media_is_musictrack:
return MEDIA_TYPE_MUSIC
return None
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
- return self.media_status.duration if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.duration if media_status else None
@property
def media_image_url(self):
"""Image url of current playing media."""
- if self.media_status is None:
+ media_status, _ = self._media_status()
+ if media_status is None:
return None
- images = self.media_status.images
+ images = media_status.images
return images[0].url if images and images[0].url else None
@property
def media_title(self):
"""Title of current playing media."""
- return self.media_status.title if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.title if media_status else None
@property
def media_artist(self):
"""Artist of current playing media (Music track only)."""
- return self.media_status.artist if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.artist if media_status else None
@property
def media_album_name(self):
"""Album of current playing media (Music track only)."""
- return self.media_status.album_name if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.album_name if media_status else None
@property
def media_album_artist(self):
"""Album artist of current playing media (Music track only)."""
- return self.media_status.album_artist if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.album_artist if media_status else None
@property
def media_track(self):
"""Track number of current playing media (Music track only)."""
- return self.media_status.track if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.track if media_status else None
@property
def media_series_title(self):
"""Return the title of the series of current playing media."""
- return self.media_status.series_title if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.series_title if media_status else None
@property
def media_season(self):
"""Season of current playing media (TV Show only)."""
- return self.media_status.season if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.season if media_status else None
@property
def media_episode(self):
"""Episode of current playing media (TV Show only)."""
- return self.media_status.episode if self.media_status else None
+ media_status, _ = self._media_status()
+ return media_status.episode if media_status else None
@property
def app_id(self):
@@ -766,17 +1107,29 @@ class CastDevice(MediaPlayerDevice):
@property
def supported_features(self):
"""Flag media player features that are supported."""
- return SUPPORT_CAST
+ support = SUPPORT_CAST
+ media_status, _ = self._media_status()
+
+ if media_status:
+ if media_status.supports_queue_next:
+ support |= SUPPORT_PREVIOUS_TRACK
+ if media_status.supports_queue_next:
+ support |= SUPPORT_NEXT_TRACK
+ if media_status.supports_seek:
+ support |= SUPPORT_SEEK
+
+ return support
@property
def media_position(self):
"""Position of current playing media in seconds."""
- if self.media_status is None or \
- not (self.media_status.player_is_playing or
- self.media_status.player_is_paused or
- self.media_status.player_is_idle):
+ media_status, _ = self._media_status()
+ if media_status is None or \
+ not (media_status.player_is_playing or
+ media_status.player_is_paused or
+ media_status.player_is_idle):
return None
- return self.media_status.current_time
+ return media_status.current_time
@property
def media_position_updated_at(self):
@@ -784,7 +1137,8 @@ class CastDevice(MediaPlayerDevice):
Returns value from homeassistant.util.dt.utcnow().
"""
- return self.media_status_received
+ _, media_status_recevied = self._media_status()
+ return media_status_recevied
@property
def unique_id(self) -> Optional[str]:
diff --git a/homeassistant/components/cert_expiry/manifest.json b/homeassistant/components/cert_expiry/manifest.json
new file mode 100644
index 00000000000..7ef2e0b7d10
--- /dev/null
+++ b/homeassistant/components/cert_expiry/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "cert_expiry",
+ "name": "Cert expiry",
+ "documentation": "https://www.home-assistant.io/components/cert_expiry",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py
index a04a631f2e9..54ba378f91c 100644
--- a/homeassistant/components/cert_expiry/sensor.py
+++ b/homeassistant/components/cert_expiry/sensor.py
@@ -1,9 +1,4 @@
-"""
-Counter for the days until an HTTPS (TLS) certificate will expire.
-
-For more details about this sensor please refer to the documentation at
-https://home-assistant.io/components/sensor.cert_expiry/
-"""
+"""Counter for the days until an HTTPS (TLS) certificate will expire."""
import logging
import socket
import ssl
diff --git a/homeassistant/components/channels/manifest.json b/homeassistant/components/channels/manifest.json
new file mode 100644
index 00000000000..152c7d3a2dc
--- /dev/null
+++ b/homeassistant/components/channels/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "channels",
+ "name": "Channels",
+ "documentation": "https://www.home-assistant.io/components/channels",
+ "requirements": [
+ "pychannels==1.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/channels/media_player.py b/homeassistant/components/channels/media_player.py
index 2f7b169601c..abd3281d11a 100644
--- a/homeassistant/components/channels/media_player.py
+++ b/homeassistant/components/channels/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing with an instance of Channels (https://getchannels.com).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.channels/
-"""
+"""Support for interfacing with an instance of getchannels.com."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.const import (
STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pychannels==1.0.0']
-
_LOGGER = logging.getLogger(__name__)
DATA_CHANNELS = 'channels'
diff --git a/homeassistant/components/channels/services.yaml b/homeassistant/components/channels/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/cisco_ios/device_tracker.py b/homeassistant/components/cisco_ios/device_tracker.py
index 1afea2c1607..5eb03970989 100644
--- a/homeassistant/components/cisco_ios/device_tracker.py
+++ b/homeassistant/components/cisco_ios/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Cisco IOS Routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.cisco_ios/
-"""
+"""Support for Cisco IOS Routers."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pexpect==4.6.0']
-
PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
diff --git a/homeassistant/components/cisco_ios/manifest.json b/homeassistant/components/cisco_ios/manifest.json
new file mode 100644
index 00000000000..9a12ba252e3
--- /dev/null
+++ b/homeassistant/components/cisco_ios/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "cisco_ios",
+ "name": "Cisco ios",
+ "documentation": "https://www.home-assistant.io/components/cisco_ios",
+ "requirements": [
+ "pexpect==4.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/cisco_mobility_express/device_tracker.py b/homeassistant/components/cisco_mobility_express/device_tracker.py
index a722a994350..4af94588d3b 100644
--- a/homeassistant/components/cisco_mobility_express/device_tracker.py
+++ b/homeassistant/components/cisco_mobility_express/device_tracker.py
@@ -9,10 +9,6 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SSL, CONF_VERIFY_SSL)
-
-REQUIREMENTS = ['ciscomobilityexpress==0.1.5']
-
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSL = False
diff --git a/homeassistant/components/cisco_mobility_express/manifest.json b/homeassistant/components/cisco_mobility_express/manifest.json
new file mode 100644
index 00000000000..d1b4687c2cd
--- /dev/null
+++ b/homeassistant/components/cisco_mobility_express/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "cisco_mobility_express",
+ "name": "Cisco mobility express",
+ "documentation": "https://www.home-assistant.io/components/cisco_mobility_express",
+ "requirements": [
+ "ciscomobilityexpress==0.1.5"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/cisco_webex_teams/__init__.py b/homeassistant/components/cisco_webex_teams/__init__.py
new file mode 100644
index 00000000000..0a8714806a1
--- /dev/null
+++ b/homeassistant/components/cisco_webex_teams/__init__.py
@@ -0,0 +1 @@
+"""Component to integrate the Cisco Webex Teams cloud."""
diff --git a/homeassistant/components/cisco_webex_teams/manifest.json b/homeassistant/components/cisco_webex_teams/manifest.json
new file mode 100644
index 00000000000..21c4efe071c
--- /dev/null
+++ b/homeassistant/components/cisco_webex_teams/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "cisco_webex_teams",
+ "name": "Cisco webex teams",
+ "documentation": "https://www.home-assistant.io/components/cisco_webex_teams",
+ "requirements": [
+ "webexteamssdk==1.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/cisco_webex_teams/notify.py b/homeassistant/components/cisco_webex_teams/notify.py
new file mode 100644
index 00000000000..22f8679f618
--- /dev/null
+++ b/homeassistant/components/cisco_webex_teams/notify.py
@@ -0,0 +1,58 @@
+"""Cisco Webex Teams notify component."""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.notify import (
+ PLATFORM_SCHEMA, BaseNotificationService, ATTR_TITLE)
+from homeassistant.const import (CONF_TOKEN)
+import homeassistant.helpers.config_validation as cv
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_ROOM_ID = 'room_id'
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_TOKEN): cv.string,
+ vol.Required(CONF_ROOM_ID): cv.string,
+})
+
+
+def get_service(hass, config, discovery_info=None):
+ """Get the CiscoWebexTeams notification service."""
+ from webexteamssdk import WebexTeamsAPI, exceptions
+ client = WebexTeamsAPI(access_token=config[CONF_TOKEN])
+ try:
+ # Validate the token & room_id
+ client.rooms.get(config[CONF_ROOM_ID])
+ except exceptions.ApiError as error:
+ _LOGGER.error(error)
+ return None
+
+ return CiscoWebexTeamsNotificationService(
+ client,
+ config[CONF_ROOM_ID])
+
+
+class CiscoWebexTeamsNotificationService(BaseNotificationService):
+ """The Cisco Webex Teams Notification Service."""
+
+ def __init__(self, client, room):
+ """Initialize the service."""
+ self.room = room
+ self.client = client
+
+ def send_message(self, message="", **kwargs):
+ """Send a message to a user."""
+ from webexteamssdk import ApiError
+ title = ""
+ if kwargs.get(ATTR_TITLE) is not None:
+ title = "{}{}".format(kwargs.get(ATTR_TITLE), "
")
+
+ try:
+ self.client.messages.create(roomId=self.room,
+ html="{}{}".format(title, message))
+ except ApiError as api_error:
+ _LOGGER.error("Could not send CiscoWebexTeams notification. "
+ "Error: %s",
+ api_error)
diff --git a/homeassistant/components/ciscospark/manifest.json b/homeassistant/components/ciscospark/manifest.json
new file mode 100644
index 00000000000..926925a7bf1
--- /dev/null
+++ b/homeassistant/components/ciscospark/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ciscospark",
+ "name": "Ciscospark",
+ "documentation": "https://www.home-assistant.io/components/ciscospark",
+ "requirements": [
+ "ciscosparkapi==0.4.2"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/ciscospark/notify.py b/homeassistant/components/ciscospark/notify.py
index 1eeb9b51f28..320c342b143 100644
--- a/homeassistant/components/ciscospark/notify.py
+++ b/homeassistant/components/ciscospark/notify.py
@@ -1,9 +1,4 @@
-"""
-Cisco Spark platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.ciscospark/
-"""
+"""Cisco Spark platform for notify component."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (ATTR_TITLE, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['ciscosparkapi==0.4.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_ROOMID = 'roomid'
diff --git a/homeassistant/components/citybikes/manifest.json b/homeassistant/components/citybikes/manifest.json
new file mode 100644
index 00000000000..ea1ceaa9531
--- /dev/null
+++ b/homeassistant/components/citybikes/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "citybikes",
+ "name": "Citybikes",
+ "documentation": "https://www.home-assistant.io/components/citybikes",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py
index 12c475e62ff..344311aa231 100644
--- a/homeassistant/components/citybikes/sensor.py
+++ b/homeassistant/components/citybikes/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for the CityBikes data.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.citybikes/
-"""
+"""Sensor for the CityBikes data."""
import asyncio
from datetime import timedelta
import logging
@@ -54,6 +49,8 @@ STATIONS_URI = 'v2/networks/{uid}?fields=network.stations'
CITYBIKES_ATTRIBUTION = "Information provided by the CityBikes Project "\
"(https://citybik.es/#about)"
+CITYBIKES_NETWORKS = 'citybikes_networks'
+
PLATFORM_SCHEMA = vol.All(
cv.has_at_least_one_key(CONF_RADIUS, CONF_STATIONS_LIST),
PLATFORM_SCHEMA.extend({
@@ -72,12 +69,12 @@ NETWORK_SCHEMA = vol.Schema({
vol.Required(ATTR_LOCATION): vol.Schema({
vol.Required(ATTR_LATITUDE): cv.latitude,
vol.Required(ATTR_LONGITUDE): cv.longitude,
- }, extra=vol.REMOVE_EXTRA),
- }, extra=vol.REMOVE_EXTRA)
+ }, extra=vol.REMOVE_EXTRA),
+}, extra=vol.REMOVE_EXTRA)
NETWORKS_RESPONSE_SCHEMA = vol.Schema({
vol.Required(ATTR_NETWORKS_LIST): [NETWORK_SCHEMA],
- })
+})
STATION_SCHEMA = vol.Schema({
vol.Required(ATTR_FREE_BIKES): cv.positive_int,
@@ -89,13 +86,13 @@ STATION_SCHEMA = vol.Schema({
vol.Required(ATTR_TIMESTAMP): cv.string,
vol.Optional(ATTR_EXTRA):
vol.Schema({vol.Optional(ATTR_UID): cv.string}, extra=vol.REMOVE_EXTRA)
- }, extra=vol.REMOVE_EXTRA)
+}, extra=vol.REMOVE_EXTRA)
STATIONS_RESPONSE_SCHEMA = vol.Schema({
vol.Required(ATTR_NETWORK): vol.Schema({
vol.Required(ATTR_STATIONS_LIST): [STATION_SCHEMA]
- }, extra=vol.REMOVE_EXTRA)
- })
+ }, extra=vol.REMOVE_EXTRA)
+})
class CityBikesRequestError(Exception):
@@ -135,18 +132,21 @@ async def async_setup_platform(hass, config, async_add_entities,
network_id = config.get(CONF_NETWORK)
stations_list = set(config.get(CONF_STATIONS_LIST, []))
radius = config.get(CONF_RADIUS, 0)
- name = config.get(CONF_NAME)
+ name = config[CONF_NAME]
if not hass.config.units.is_metric:
radius = distance.convert(radius, LENGTH_FEET, LENGTH_METERS)
+ # Create a single instance of CityBikesNetworks.
+ networks = hass.data.setdefault(
+ CITYBIKES_NETWORKS, CityBikesNetworks(hass))
+
if not network_id:
- network_id = await CityBikesNetwork.get_closest_network_id(
- hass, latitude, longitude)
+ network_id = await networks.get_closest_network_id(latitude, longitude)
if network_id not in hass.data[PLATFORM][MONITORED_NETWORKS]:
network = CityBikesNetwork(hass, network_id)
hass.data[PLATFORM][MONITORED_NETWORKS][network_id] = network
- hass.async_add_job(network.async_refresh)
+ hass.async_create_task(network.async_refresh())
async_track_time_interval(hass, network.async_refresh, SCAN_INTERVAL)
else:
network = hass.data[PLATFORM][MONITORED_NETWORKS][network_id]
@@ -163,29 +163,37 @@ async def async_setup_platform(hass, config, async_add_entities,
if radius > dist or stations_list.intersection(
(station_id, station_uid)):
- devices.append(CityBikesStation(hass, network, station_id, name))
+ if name:
+ uid = "_".join([network.network_id, name, station_id])
+ else:
+ uid = "_".join([network.network_id, station_id])
+ entity_id = async_generate_entity_id(
+ ENTITY_ID_FORMAT, uid, hass=hass)
+ devices.append(CityBikesStation(network, station_id, entity_id))
async_add_entities(devices, True)
-class CityBikesNetwork:
- """Thin wrapper around a CityBikes network object."""
+class CityBikesNetworks:
+ """Represent all CityBikes networks."""
- NETWORKS_LIST = None
- NETWORKS_LIST_LOADING = asyncio.Condition()
+ def __init__(self, hass):
+ """Initialize the networks instance."""
+ self.hass = hass
+ self.networks = None
+ self.networks_loading = asyncio.Condition(loop=hass.loop)
- @classmethod
- async def get_closest_network_id(cls, hass, latitude, longitude):
+ async def get_closest_network_id(self, latitude, longitude):
"""Return the id of the network closest to provided location."""
try:
- await cls.NETWORKS_LIST_LOADING.acquire()
- if cls.NETWORKS_LIST is None:
+ await self.networks_loading.acquire()
+ if self.networks is None:
networks = await async_citybikes_request(
- hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
- cls.NETWORKS_LIST = networks[ATTR_NETWORKS_LIST]
+ self.hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
+ self.networks = networks[ATTR_NETWORKS_LIST]
result = None
minimum_dist = None
- for network in cls.NETWORKS_LIST:
+ for network in self.networks:
network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE]
network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE]
dist = location.distance(
@@ -198,14 +206,18 @@ class CityBikesNetwork:
except CityBikesRequestError:
raise PlatformNotReady
finally:
- cls.NETWORKS_LIST_LOADING.release()
+ self.networks_loading.release()
+
+
+class CityBikesNetwork:
+ """Thin wrapper around a CityBikes network object."""
def __init__(self, hass, network_id):
"""Initialize the network object."""
self.hass = hass
self.network_id = network_id
self.stations = []
- self.ready = asyncio.Event()
+ self.ready = asyncio.Event(loop=hass.loop)
async def async_refresh(self, now=None):
"""Refresh the state of the network."""
@@ -225,37 +237,29 @@ class CityBikesNetwork:
class CityBikesStation(Entity):
"""CityBikes API Sensor."""
- def __init__(self, hass, network, station_id, base_name=''):
+ def __init__(self, network, station_id, entity_id):
"""Initialize the sensor."""
self._network = network
self._station_id = station_id
self._station_data = {}
- if base_name:
- uid = "_".join([network.network_id, base_name, station_id])
- else:
- uid = "_".join([network.network_id, station_id])
- self.entity_id = async_generate_entity_id(
- ENTITY_ID_FORMAT, uid, hass=hass)
+ self.entity_id = entity_id
@property
def state(self):
"""Return the state of the sensor."""
- return self._station_data.get(ATTR_FREE_BIKES, None)
+ return self._station_data.get(ATTR_FREE_BIKES)
@property
def name(self):
"""Return the name of the sensor."""
- if ATTR_NAME in self._station_data:
- return self._station_data[ATTR_NAME]
- return None
+ return self._station_data.get(ATTR_NAME)
async def async_update(self):
"""Update station state."""
- if self._network.ready.is_set():
- for station in self._network.stations:
- if station[ATTR_ID] == self._station_id:
- self._station_data = station
- break
+ for station in self._network.stations:
+ if station[ATTR_ID] == self._station_id:
+ self._station_data = station
+ break
@property
def device_state_attributes(self):
diff --git a/homeassistant/components/clementine/manifest.json b/homeassistant/components/clementine/manifest.json
new file mode 100644
index 00000000000..4d835ed4e7c
--- /dev/null
+++ b/homeassistant/components/clementine/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "clementine",
+ "name": "Clementine",
+ "documentation": "https://www.home-assistant.io/components/clementine",
+ "requirements": [
+ "python-clementine-remote==1.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/clementine/media_player.py b/homeassistant/components/clementine/media_player.py
index 24df7c24611..fc6e27be1bd 100644
--- a/homeassistant/components/clementine/media_player.py
+++ b/homeassistant/components/clementine/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Clementine Music Player as media player.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.clementine/
-"""
+"""Support for Clementine Music Player as media player."""
from datetime import timedelta
import logging
import time
@@ -21,8 +16,6 @@ from homeassistant.const import (
STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-clementine-remote==1.0.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Clementine Remote'
diff --git a/homeassistant/components/clickatell/manifest.json b/homeassistant/components/clickatell/manifest.json
new file mode 100644
index 00000000000..ffd550eebee
--- /dev/null
+++ b/homeassistant/components/clickatell/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "clickatell",
+ "name": "Clickatell",
+ "documentation": "https://www.home-assistant.io/components/clickatell",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/clickatell/notify.py b/homeassistant/components/clickatell/notify.py
index e473e54a3b7..b512a288ed5 100644
--- a/homeassistant/components/clickatell/notify.py
+++ b/homeassistant/components/clickatell/notify.py
@@ -1,9 +1,4 @@
-"""
-Clickatell platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.clickatell/
-"""
+"""Clickatell platform for notify component."""
import logging
import requests
diff --git a/homeassistant/components/clicksend/manifest.json b/homeassistant/components/clicksend/manifest.json
new file mode 100644
index 00000000000..38319825094
--- /dev/null
+++ b/homeassistant/components/clicksend/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "clicksend",
+ "name": "Clicksend",
+ "documentation": "https://www.home-assistant.io/components/clicksend",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/clicksend/notify.py b/homeassistant/components/clicksend/notify.py
index 3b2cdb7496d..111ae63601f 100644
--- a/homeassistant/components/clicksend/notify.py
+++ b/homeassistant/components/clicksend/notify.py
@@ -1,9 +1,4 @@
-"""
-Clicksend platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.clicksend/
-"""
+"""Clicksend platform for notify component."""
import json
import logging
diff --git a/homeassistant/components/clicksend_tts/manifest.json b/homeassistant/components/clicksend_tts/manifest.json
new file mode 100644
index 00000000000..c2a86f426e4
--- /dev/null
+++ b/homeassistant/components/clicksend_tts/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "clicksend_tts",
+ "name": "Clicksend tts",
+ "documentation": "https://www.home-assistant.io/components/clicksend_tts",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/clicksend_tts/notify.py b/homeassistant/components/clicksend_tts/notify.py
index 93e5126bbab..feb4481fb56 100644
--- a/homeassistant/components/clicksend_tts/notify.py
+++ b/homeassistant/components/clicksend_tts/notify.py
@@ -1,11 +1,4 @@
-"""
-clicksend_tts platform for notify component.
-
-This platform sends text to speech audio messages through clicksend
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.clicksend_tts/
-"""
+"""clicksend_tts platform for notify component."""
import json
import logging
diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py
index 0283359b1f2..18b56049f83 100644
--- a/homeassistant/components/climate/__init__.py
+++ b/homeassistant/components/climate/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provides functionality to interact with climate devices.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/climate/
-"""
+"""Provides functionality to interact with climate devices."""
from datetime import timedelta
import logging
import functools as ft
diff --git a/homeassistant/components/climate/manifest.json b/homeassistant/components/climate/manifest.json
new file mode 100644
index 00000000000..ca5312e7670
--- /dev/null
+++ b/homeassistant/components/climate/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "climate",
+ "name": "Climate",
+ "documentation": "https://www.home-assistant.io/components/climate",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml
index 1460181ddc2..c0dd231ef95 100644
--- a/homeassistant/components/climate/services.yaml
+++ b/homeassistant/components/climate/services.yaml
@@ -80,7 +80,6 @@ set_swing_mode:
example: 'climate.nest'
swing_mode:
description: New value of swing mode.
- example:
turn_on:
description: Turn climate device on.
diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py
index 76a768385f8..1ad76c3d7aa 100644
--- a/homeassistant/components/cloud/__init__.py
+++ b/homeassistant/components/cloud/__init__.py
@@ -24,9 +24,6 @@ from .const import (
CONF_USER_POOL_ID, DOMAIN, MODE_DEV, MODE_PROD)
from .prefs import CloudPreferences
-REQUIREMENTS = ['hass-nabucasa==0.11']
-DEPENDENCIES = ['http']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_MODE = MODE_PROD
@@ -187,12 +184,17 @@ async def async_setup(hass, config):
await cloud.remote.disconnect()
await prefs.async_update(remote_enabled=False)
- hass.services.async_register(
+ hass.helpers.service.async_register_admin_service(
DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler)
- hass.services.async_register(
+ hass.helpers.service.async_register_admin_service(
DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler)
+ async def _on_connect():
+ """Discover RemoteUI binary sensor."""
+ hass.async_create_task(hass.helpers.discovery.async_load_platform(
+ 'binary_sensor', DOMAIN, {}, config))
+
+ cloud.iot.register_on_connect(_on_connect)
+
await http_api.async_setup(hass)
- hass.async_create_task(hass.helpers.discovery.async_load_platform(
- 'binary_sensor', DOMAIN, {}, config))
return True
diff --git a/homeassistant/components/cloud/binary_sensor.py b/homeassistant/components/cloud/binary_sensor.py
index 19a6528e321..3e4aaf9cc84 100644
--- a/homeassistant/components/cloud/binary_sensor.py
+++ b/homeassistant/components/cloud/binary_sensor.py
@@ -6,8 +6,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DISPATCHER_REMOTE_UPDATE, DOMAIN
-DEPENDENCIES = ['cloud']
-
WAIT_UNTIL_CHANGE = 3
diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py
index da89f8331a9..5bbd7bb48fa 100644
--- a/homeassistant/components/cloud/client.py
+++ b/homeassistant/components/cloud/client.py
@@ -102,10 +102,14 @@ class CloudClient(Interface):
self._google_config = ga_h.Config(
should_expose=should_expose,
- allow_unlock=self._prefs.google_allow_unlock,
+ secure_devices_pin=self._prefs.google_secure_devices_pin,
entity_config=google_conf.get(CONF_ENTITY_CONFIG),
)
+ # Set it to the latest.
+ self._google_config.secure_devices_pin = \
+ self._prefs.google_secure_devices_pin
+
return self._google_config
@property
@@ -150,8 +154,11 @@ class CloudClient(Interface):
)
# Fix AgentUserId
- cloud = self._hass.data[DOMAIN]
- answer['payload']['agentUserId'] = cloud.claims['cognito:username']
+ try:
+ cloud = self._hass.data[DOMAIN]
+ answer['payload']['agentUserId'] = cloud.claims['cognito:username']
+ except (TypeError, KeyError):
+ return ga.turned_off_response(payload)
return answer
diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py
index 1286832c0c7..5002286edb9 100644
--- a/homeassistant/components/cloud/const.py
+++ b/homeassistant/components/cloud/const.py
@@ -5,7 +5,7 @@ REQUEST_TIMEOUT = 10
PREF_ENABLE_ALEXA = 'alexa_enabled'
PREF_ENABLE_GOOGLE = 'google_enabled'
PREF_ENABLE_REMOTE = 'remote_enabled'
-PREF_GOOGLE_ALLOW_UNLOCK = 'google_allow_unlock'
+PREF_GOOGLE_SECURE_DEVICES_PIN = 'google_secure_devices_pin'
PREF_CLOUDHOOKS = 'cloudhooks'
PREF_CLOUD_USER = 'cloud_user'
diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py
index 212bdfb4bf8..bf9b7833527 100644
--- a/homeassistant/components/cloud/http_api.py
+++ b/homeassistant/components/cloud/http_api.py
@@ -14,11 +14,12 @@ from homeassistant.components.http.data_validator import (
RequestDataValidator)
from homeassistant.components import websocket_api
from homeassistant.components.alexa import smart_home as alexa_sh
-from homeassistant.components.google_assistant import smart_home as google_sh
+from homeassistant.components.google_assistant import (
+ const as google_const)
from .const import (
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
- PREF_GOOGLE_ALLOW_UNLOCK, InvalidTrustedNetworks)
+ PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks)
_LOGGER = logging.getLogger(__name__)
@@ -29,15 +30,6 @@ SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
})
-WS_TYPE_UPDATE_PREFS = 'cloud/update_prefs'
-SCHEMA_WS_UPDATE_PREFS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): WS_TYPE_UPDATE_PREFS,
- vol.Optional(PREF_ENABLE_GOOGLE): bool,
- vol.Optional(PREF_ENABLE_ALEXA): bool,
- vol.Optional(PREF_GOOGLE_ALLOW_UNLOCK): bool,
-})
-
-
WS_TYPE_SUBSCRIPTION = 'cloud/subscription'
SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_SUBSCRIPTION,
@@ -76,9 +68,7 @@ async def async_setup(hass):
SCHEMA_WS_SUBSCRIPTION
)
hass.components.websocket_api.async_register_command(
- WS_TYPE_UPDATE_PREFS, websocket_update_prefs,
- SCHEMA_WS_UPDATE_PREFS
- )
+ websocket_update_prefs)
hass.components.websocket_api.async_register_command(
WS_TYPE_HOOK_CREATE, websocket_hook_create,
SCHEMA_WS_HOOK_CREATE
@@ -105,6 +95,8 @@ async def async_setup(hass):
(400, "User does not exist."),
auth.UserNotConfirmed:
(400, 'Email not confirmed.'),
+ auth.UserExists:
+ (400, 'An account with the given email already exists.'),
auth.Unauthenticated:
(401, 'Authentication failed.'),
auth.PasswordChangeRequired:
@@ -355,6 +347,12 @@ async def websocket_subscription(hass, connection, msg):
@_require_cloud_login
@websocket_api.async_response
+@websocket_api.websocket_command({
+ vol.Required('type'): 'cloud/update_prefs',
+ vol.Optional(PREF_ENABLE_GOOGLE): bool,
+ vol.Optional(PREF_ENABLE_ALEXA): bool,
+ vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str),
+})
async def websocket_update_prefs(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
@@ -413,7 +411,7 @@ def _account_data(cloud):
'cloud': cloud.iot.state,
'prefs': client.prefs.as_dict(),
'google_entities': client.google_user_config['filter'].config,
- 'google_domains': list(google_sh.DOMAIN_TO_GOOGLE_TYPES),
+ 'google_domains': list(google_const.DOMAIN_TO_GOOGLE_TYPES),
'alexa_entities': client.alexa_config.should_expose.config,
'alexa_domains': list(alexa_sh.ENTITY_ADAPTERS),
'remote_domain': remote.instance_domain,
@@ -422,6 +420,7 @@ def _account_data(cloud):
}
+@websocket_api.require_admin
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
@@ -436,6 +435,7 @@ async def websocket_remote_connect(hass, connection, msg):
connection.send_result(msg['id'], _account_data(cloud))
+@websocket_api.require_admin
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json
new file mode 100644
index 00000000000..863e3e86da4
--- /dev/null
+++ b/homeassistant/components/cloud/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "cloud",
+ "name": "Cloud",
+ "documentation": "https://www.home-assistant.io/components/cloud",
+ "requirements": [
+ "hass-nabucasa==0.12"
+ ],
+ "dependencies": [
+ "http",
+ "webhook"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py
index b0244f6b1fb..0e2abae15b0 100644
--- a/homeassistant/components/cloud/prefs.py
+++ b/homeassistant/components/cloud/prefs.py
@@ -3,7 +3,7 @@ from ipaddress import ip_address
from .const import (
DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, PREF_ENABLE_REMOTE,
- PREF_GOOGLE_ALLOW_UNLOCK, PREF_CLOUDHOOKS, PREF_CLOUD_USER,
+ PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_CLOUDHOOKS, PREF_CLOUD_USER,
InvalidTrustedNetworks)
STORAGE_KEY = DOMAIN
@@ -29,7 +29,7 @@ class CloudPreferences:
PREF_ENABLE_ALEXA: True,
PREF_ENABLE_GOOGLE: True,
PREF_ENABLE_REMOTE: False,
- PREF_GOOGLE_ALLOW_UNLOCK: False,
+ PREF_GOOGLE_SECURE_DEVICES_PIN: None,
PREF_CLOUDHOOKS: {},
PREF_CLOUD_USER: None,
}
@@ -38,14 +38,14 @@ class CloudPreferences:
async def async_update(self, *, google_enabled=_UNDEF,
alexa_enabled=_UNDEF, remote_enabled=_UNDEF,
- google_allow_unlock=_UNDEF, cloudhooks=_UNDEF,
+ google_secure_devices_pin=_UNDEF, cloudhooks=_UNDEF,
cloud_user=_UNDEF):
"""Update user preferences."""
for key, value in (
(PREF_ENABLE_GOOGLE, google_enabled),
(PREF_ENABLE_ALEXA, alexa_enabled),
(PREF_ENABLE_REMOTE, remote_enabled),
- (PREF_GOOGLE_ALLOW_UNLOCK, google_allow_unlock),
+ (PREF_GOOGLE_SECURE_DEVICES_PIN, google_secure_devices_pin),
(PREF_CLOUDHOOKS, cloudhooks),
(PREF_CLOUD_USER, cloud_user),
):
@@ -85,9 +85,9 @@ class CloudPreferences:
return self._prefs[PREF_ENABLE_GOOGLE]
@property
- def google_allow_unlock(self):
+ def google_secure_devices_pin(self):
"""Return if Google is allowed to unlock locks."""
- return self._prefs.get(PREF_GOOGLE_ALLOW_UNLOCK, False)
+ return self._prefs.get(PREF_GOOGLE_SECURE_DEVICES_PIN)
@property
def cloudhooks(self):
diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py
index 363e7c5eeb1..ce88f820fe3 100644
--- a/homeassistant/components/cloudflare/__init__.py
+++ b/homeassistant/components/cloudflare/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_API_KEY, CONF_EMAIL, CONF_ZONE
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['pycfdns==0.0.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_RECORDS = 'records'
diff --git a/homeassistant/components/cloudflare/manifest.json b/homeassistant/components/cloudflare/manifest.json
new file mode 100644
index 00000000000..7716ae65c4e
--- /dev/null
+++ b/homeassistant/components/cloudflare/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "cloudflare",
+ "name": "Cloudflare",
+ "documentation": "https://www.home-assistant.io/components/cloudflare",
+ "requirements": [
+ "pycfdns==0.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/cloudflare/services.yaml b/homeassistant/components/cloudflare/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/cmus/manifest.json b/homeassistant/components/cmus/manifest.json
new file mode 100644
index 00000000000..1528f4252b1
--- /dev/null
+++ b/homeassistant/components/cmus/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "cmus",
+ "name": "Cmus",
+ "documentation": "https://www.home-assistant.io/components/cmus",
+ "requirements": [
+ "pycmus==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py
index 20b292749b4..4f1dfc50536 100644
--- a/homeassistant/components/cmus/media_player.py
+++ b/homeassistant/components/cmus/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interacting with and controlling the cmus music player.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.cmus/
-"""
+"""Support for interacting with and controlling the cmus music player."""
import logging
import voluptuous as vol
@@ -19,8 +14,6 @@ from homeassistant.const import (
STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pycmus==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'cmus'
diff --git a/homeassistant/components/co2signal/manifest.json b/homeassistant/components/co2signal/manifest.json
new file mode 100644
index 00000000000..ac42e374fdd
--- /dev/null
+++ b/homeassistant/components/co2signal/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "co2signal",
+ "name": "Co2signal",
+ "documentation": "https://www.home-assistant.io/components/co2signal",
+ "requirements": [
+ "co2signal==0.4.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py
index 7b4cd67bd70..990521d0418 100644
--- a/homeassistant/components/co2signal/sensor.py
+++ b/homeassistant/components/co2signal/sensor.py
@@ -1,8 +1,4 @@
-"""
-Support for the CO2signal platform.
-
-For more details about this platform, please refer to the documentation
-"""
+"""Support for the CO2signal platform."""
import logging
import voluptuous as vol
@@ -15,8 +11,6 @@ from homeassistant.helpers.entity import Entity
CONF_COUNTRY_CODE = "country_code"
-REQUIREMENTS = ['co2signal==0.4.2']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Data provided by CO2signal'
diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py
index 40d04eadb3a..21efd5f9b8e 100644
--- a/homeassistant/components/coinbase/__init__.py
+++ b/homeassistant/components/coinbase/__init__.py
@@ -9,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
-REQUIREMENTS = ['coinbase==2.1.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'coinbase'
diff --git a/homeassistant/components/coinbase/manifest.json b/homeassistant/components/coinbase/manifest.json
new file mode 100644
index 00000000000..5f8a189c7d1
--- /dev/null
+++ b/homeassistant/components/coinbase/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "coinbase",
+ "name": "Coinbase",
+ "documentation": "https://www.home-assistant.io/components/coinbase",
+ "requirements": [
+ "coinbase==2.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py
index 54af94944d6..9470999efbb 100644
--- a/homeassistant/components/coinbase/sensor.py
+++ b/homeassistant/components/coinbase/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Coinbase sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.coinbase/
-"""
+"""Support for Coinbase sensors."""
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
@@ -22,7 +17,6 @@ DEFAULT_COIN_ICON = 'mdi:coin'
ATTRIBUTION = "Data provided by coinbase.com"
DATA_COINBASE = 'coinbase_cache'
-DEPENDENCIES = ['coinbase']
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/coinmarketcap/manifest.json b/homeassistant/components/coinmarketcap/manifest.json
new file mode 100644
index 00000000000..0afb1b1c28f
--- /dev/null
+++ b/homeassistant/components/coinmarketcap/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "coinmarketcap",
+ "name": "Coinmarketcap",
+ "documentation": "https://www.home-assistant.io/components/coinmarketcap",
+ "requirements": [
+ "coinmarketcap==5.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/coinmarketcap/sensor.py b/homeassistant/components/coinmarketcap/sensor.py
index 9143405a553..4d8af5a721d 100644
--- a/homeassistant/components/coinmarketcap/sensor.py
+++ b/homeassistant/components/coinmarketcap/sensor.py
@@ -1,9 +1,4 @@
-"""
-Details about crypto currencies from CoinMarketCap.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.coinmarketcap/
-"""
+"""Details about crypto currencies from CoinMarketCap."""
import logging
from datetime import timedelta
from urllib.error import HTTPError
@@ -16,8 +11,6 @@ from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_DISPLAY_CURRENCY)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['coinmarketcap==5.0.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_VOLUME_24H = 'volume_24h'
diff --git a/homeassistant/components/comed_hourly_pricing/manifest.json b/homeassistant/components/comed_hourly_pricing/manifest.json
new file mode 100644
index 00000000000..47c7931a0e9
--- /dev/null
+++ b/homeassistant/components/comed_hourly_pricing/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "comed_hourly_pricing",
+ "name": "Comed hourly pricing",
+ "documentation": "https://www.home-assistant.io/components/comed_hourly_pricing",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py
index 1771fd0f1a3..384aadd8bf4 100644
--- a/homeassistant/components/comed_hourly_pricing/sensor.py
+++ b/homeassistant/components/comed_hourly_pricing/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for ComEd Hourly Pricing data.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.comed_hourly_pricing/
-"""
+"""Support for ComEd Hourly Pricing data."""
import asyncio
from datetime import timedelta
import json
diff --git a/homeassistant/components/comfoconnect/__init__.py b/homeassistant/components/comfoconnect/__init__.py
index 64ebec18545..3c50f3fb723 100644
--- a/homeassistant/components/comfoconnect/__init__.py
+++ b/homeassistant/components/comfoconnect/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
-REQUIREMENTS = ['pycomfoconnect==0.3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'comfoconnect'
diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py
index 88dcffcfd21..56175f0bca0 100644
--- a/homeassistant/components/comfoconnect/fan.py
+++ b/homeassistant/components/comfoconnect/fan.py
@@ -10,8 +10,6 @@ from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['comfoconnect']
-
SPEED_MAPPING = {
0: SPEED_OFF,
1: SPEED_LOW,
diff --git a/homeassistant/components/comfoconnect/manifest.json b/homeassistant/components/comfoconnect/manifest.json
new file mode 100644
index 00000000000..03319aeffa8
--- /dev/null
+++ b/homeassistant/components/comfoconnect/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "comfoconnect",
+ "name": "Comfoconnect",
+ "documentation": "https://www.home-assistant.io/components/comfoconnect",
+ "requirements": [
+ "pycomfoconnect==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py
index edb96b8d279..db2a9393e2b 100644
--- a/homeassistant/components/comfoconnect/sensor.py
+++ b/homeassistant/components/comfoconnect/sensor.py
@@ -12,8 +12,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['comfoconnect']
-
SENSOR_TYPES = {}
diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py
index 21ee1312e7a..860367d8091 100644
--- a/homeassistant/components/command_line/binary_sensor.py
+++ b/homeassistant/components/command_line/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for custom shell commands to retrieve values.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.command_line/
-"""
+"""Support for custom shell commands to retrieve values."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py
index 4f4fca1b27a..7f3c5279905 100644
--- a/homeassistant/components/command_line/cover.py
+++ b/homeassistant/components/command_line/cover.py
@@ -1,9 +1,4 @@
-"""
-Support for command line covers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/cover.command_line/
-"""
+"""Support for command line covers."""
import logging
import subprocess
diff --git a/homeassistant/components/command_line/manifest.json b/homeassistant/components/command_line/manifest.json
new file mode 100644
index 00000000000..ff94522210d
--- /dev/null
+++ b/homeassistant/components/command_line/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "command_line",
+ "name": "Command line",
+ "documentation": "https://www.home-assistant.io/components/command_line",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/command_line/notify.py b/homeassistant/components/command_line/notify.py
index 7ea5a6d8880..941be72aa81 100644
--- a/homeassistant/components/command_line/notify.py
+++ b/homeassistant/components/command_line/notify.py
@@ -1,9 +1,4 @@
-"""
-Support for command line notification services.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.command_line/
-"""
+"""Support for command line notification services."""
import logging
import subprocess
diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py
index e1d151410b1..587cfe53d3c 100644
--- a/homeassistant/components/command_line/sensor.py
+++ b/homeassistant/components/command_line/sensor.py
@@ -1,9 +1,4 @@
-"""
-Allows to configure custom shell commands to turn a value for a sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.command_line/
-"""
+"""Allows to configure custom shell commands to turn a value for a sensor."""
import collections
from datetime import timedelta
import json
@@ -170,7 +165,7 @@ class CommandSensorData:
command = str(' '.join([prog] + shlex.split(rendered_args)))
shell = True
try:
- _LOGGER.info("Running command: %s", command)
+ _LOGGER.debug("Running command: %s", command)
return_value = subprocess.check_output(
command, shell=shell, timeout=self.timeout)
self.value = return_value.strip().decode('utf-8')
diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py
index 4edbd79ee0c..8d97198ad66 100644
--- a/homeassistant/components/command_line/switch.py
+++ b/homeassistant/components/command_line/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for custom shell commands to turn a switch on/off.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.command_line/
-"""
+"""Support for custom shell commands to turn a switch on/off."""
import logging
import subprocess
diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py
index 155d6b6ae49..c56e7e71531 100644
--- a/homeassistant/components/concord232/alarm_control_panel.py
+++ b/homeassistant/components/concord232/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for Concord232 alarm control panels.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.concord232/
-"""
+"""Support for Concord232 alarm control panels."""
import datetime
import logging
@@ -14,22 +9,23 @@ import homeassistant.components.alarm_control_panel as alarm
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
- STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
-
-REQUIREMENTS = ['concord232==0.15']
+ CONF_HOST, CONF_NAME, CONF_PORT, CONF_CODE, CONF_MODE,
+ STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'CONCORD232'
DEFAULT_PORT = 5007
+DEFAULT_MODE = 'audible'
SCAN_INTERVAL = datetime.timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_CODE): cv.string,
+ vol.Optional(CONF_MODE, default=DEFAULT_MODE): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
@@ -37,13 +33,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Concord232 alarm control panel platform."""
name = config.get(CONF_NAME)
+ code = config.get(CONF_CODE)
+ mode = config.get(CONF_MODE)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
url = 'http://{}:{}'.format(host, port)
try:
- add_entities([Concord232Alarm(url, name)], True)
+ add_entities([Concord232Alarm(url, name, code, mode)], True)
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
@@ -51,12 +49,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class Concord232Alarm(alarm.AlarmControlPanel):
"""Representation of the Concord232-based alarm panel."""
- def __init__(self, url, name):
+ def __init__(self, url, name, code, mode):
"""Initialize the Concord232 alarm panel."""
from concord232 import client as concord232_client
self._state = None
self._name = name
+ self._code = code
+ self._mode = mode
self._url = url
self._alarm = concord232_client.Client(self._url)
self._alarm.partitions = self._alarm.list_partitions()
@@ -98,12 +98,35 @@ class Concord232Alarm(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None):
"""Send disarm command."""
+ if not self._validate_code(code, STATE_ALARM_DISARMED):
+ return
self._alarm.disarm(code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
- self._alarm.arm('stay')
+ if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
+ return
+ if self._mode == 'silent':
+ self._alarm.arm('stay', 'silent')
+ else:
+ self._alarm.arm('stay')
def alarm_arm_away(self, code=None):
"""Send arm away command."""
+ if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
+ return
self._alarm.arm('away')
+
+ def _validate_code(self, code, state):
+ """Validate given code."""
+ if self._code is None:
+ return True
+ if isinstance(self._code, str):
+ alarm_code = self._code
+ else:
+ alarm_code = self._code.render(from_state=self._state,
+ to_state=state)
+ check = not alarm_code or code == alarm_code
+ if not check:
+ _LOGGER.warning("Invalid code given for %s", state)
+ return check
diff --git a/homeassistant/components/concord232/binary_sensor.py b/homeassistant/components/concord232/binary_sensor.py
index 3e4d0526db4..ae464da9798 100644
--- a/homeassistant/components/concord232/binary_sensor.py
+++ b/homeassistant/components/concord232/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for exposing Concord232 elements as sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.concord232/
-"""
+"""Support for exposing Concord232 elements as sensors."""
import datetime
import logging
@@ -15,8 +10,6 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['concord232==0.15']
-
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_ZONES = 'exclude_zones'
diff --git a/homeassistant/components/concord232/manifest.json b/homeassistant/components/concord232/manifest.json
new file mode 100644
index 00000000000..f26da49d3f1
--- /dev/null
+++ b/homeassistant/components/concord232/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "concord232",
+ "name": "Concord232",
+ "documentation": "https://www.home-assistant.io/components/concord232",
+ "requirements": [
+ "concord232==0.15"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py
index 7807c527370..3752d5d37bf 100644
--- a/homeassistant/components/config/__init__.py
+++ b/homeassistant/components/config/__init__.py
@@ -12,7 +12,6 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
-DEPENDENCIES = ['http']
SECTIONS = (
'area_registry',
'auth',
diff --git a/homeassistant/components/config/manifest.json b/homeassistant/components/config/manifest.json
new file mode 100644
index 00000000000..9c0c50a2595
--- /dev/null
+++ b/homeassistant/components/config/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "config",
+ "name": "Config",
+ "documentation": "https://www.home-assistant.io/components/config",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/config/services.yaml b/homeassistant/components/config/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/configurator/manifest.json b/homeassistant/components/configurator/manifest.json
new file mode 100644
index 00000000000..f01fe7324fa
--- /dev/null
+++ b/homeassistant/components/configurator/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "configurator",
+ "name": "Configurator",
+ "documentation": "https://www.home-assistant.io/components/configurator",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/configurator/services.yaml b/homeassistant/components/configurator/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py
index bb2d692f249..bd577127fa0 100644
--- a/homeassistant/components/conversation/__init__.py
+++ b/homeassistant/components/conversation/__init__.py
@@ -21,7 +21,6 @@ _LOGGER = logging.getLogger(__name__)
ATTR_TEXT = 'text'
-DEPENDENCIES = ['http']
DOMAIN = 'conversation'
REGEX_TURN_COMMAND = re.compile(r'turn (?P(?: |\w)+) (?P\w+)')
diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json
new file mode 100644
index 00000000000..ddd3b6205ef
--- /dev/null
+++ b/homeassistant/components/conversation/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "conversation",
+ "name": "Conversation",
+ "documentation": "https://www.home-assistant.io/components/conversation",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/coolmaster/climate.py b/homeassistant/components/coolmaster/climate.py
index fd00c9f22c4..d6402bd893c 100644
--- a/homeassistant/components/coolmaster/climate.py
+++ b/homeassistant/components/coolmaster/climate.py
@@ -1,9 +1,4 @@
-"""
-CoolMasterNet platform that offers control of CoolMasteNet Climate Devices.
-
-For more details about this platform, please refer to the documentation
-https://www.home-assistant.io/components/climate.coolmaster/
-"""
+"""CoolMasterNet platform to control of CoolMasteNet Climate Devices."""
import logging
@@ -18,8 +13,6 @@ from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pycoolmasternet==0.0.4']
-
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
diff --git a/homeassistant/components/coolmaster/manifest.json b/homeassistant/components/coolmaster/manifest.json
new file mode 100644
index 00000000000..9489dc72689
--- /dev/null
+++ b/homeassistant/components/coolmaster/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "coolmaster",
+ "name": "Coolmaster",
+ "documentation": "https://www.home-assistant.io/components/coolmaster",
+ "requirements": [
+ "pycoolmasternet==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@OnFreund"
+ ]
+}
diff --git a/homeassistant/components/counter/manifest.json b/homeassistant/components/counter/manifest.json
new file mode 100644
index 00000000000..ae7066ea82d
--- /dev/null
+++ b/homeassistant/components/counter/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "counter",
+ "name": "Counter",
+ "documentation": "https://www.home-assistant.io/components/counter",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py
index 8b4031f09ed..8609d3c9cf6 100644
--- a/homeassistant/components/cover/__init__.py
+++ b/homeassistant/components/cover/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for Cover devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/cover/
-"""
+"""Support for Cover devices."""
from datetime import timedelta
import functools as ft
import logging
@@ -27,7 +22,6 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'cover'
-DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=15)
GROUP_NAME_ALL_COVERS = 'all covers'
diff --git a/homeassistant/components/cover/manifest.json b/homeassistant/components/cover/manifest.json
new file mode 100644
index 00000000000..da5a644334c
--- /dev/null
+++ b/homeassistant/components/cover/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "cover",
+ "name": "Cover",
+ "documentation": "https://www.home-assistant.io/components/cover",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/cppm_tracker/device_tracker.py b/homeassistant/components/cppm_tracker/device_tracker.py
index 2ca0ebf62e5..608ce6dad6b 100755
--- a/homeassistant/components/cppm_tracker/device_tracker.py
+++ b/homeassistant/components/cppm_tracker/device_tracker.py
@@ -1,8 +1,4 @@
-"""
-Support for ClearPass Policy Manager.
-
-Allows tracking devices with CPPM.
-"""
+"""Support for ClearPass Policy Manager."""
import logging
from datetime import timedelta
@@ -15,8 +11,6 @@ from homeassistant.const import (
CONF_HOST, CONF_API_KEY
)
-REQUIREMENTS = ['clearpasspy==1.0.2']
-
SCAN_INTERVAL = timedelta(seconds=120)
CLIENT_ID = 'client_id'
diff --git a/homeassistant/components/cppm_tracker/manifest.json b/homeassistant/components/cppm_tracker/manifest.json
new file mode 100644
index 00000000000..5a1bdbf5a45
--- /dev/null
+++ b/homeassistant/components/cppm_tracker/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "cppm_tracker",
+ "name": "Cppm tracker",
+ "documentation": "https://www.home-assistant.io/components/cppm_tracker",
+ "requirements": [
+ "clearpasspy==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/cpuspeed/manifest.json b/homeassistant/components/cpuspeed/manifest.json
new file mode 100644
index 00000000000..9034cb7740d
--- /dev/null
+++ b/homeassistant/components/cpuspeed/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "cpuspeed",
+ "name": "Cpuspeed",
+ "documentation": "https://www.home-assistant.io/components/cpuspeed",
+ "requirements": [
+ "py-cpuinfo==5.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py
index 98d22c20d15..ef9cb218cd7 100644
--- a/homeassistant/components/cpuspeed/sensor.py
+++ b/homeassistant/components/cpuspeed/sensor.py
@@ -8,8 +8,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['py-cpuinfo==5.0.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_BRAND = 'Brand'
diff --git a/homeassistant/components/crimereports/manifest.json b/homeassistant/components/crimereports/manifest.json
new file mode 100644
index 00000000000..0f74216b9b2
--- /dev/null
+++ b/homeassistant/components/crimereports/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "crimereports",
+ "name": "Crimereports",
+ "documentation": "https://www.home-assistant.io/components/crimereports",
+ "requirements": [
+ "crimereports==1.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/crimereports/sensor.py b/homeassistant/components/crimereports/sensor.py
index 13934675517..5e25d800247 100644
--- a/homeassistant/components/crimereports/sensor.py
+++ b/homeassistant/components/crimereports/sensor.py
@@ -16,8 +16,6 @@ from homeassistant.util.distance import convert
from homeassistant.util.dt import now
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['crimereports==1.0.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'crimereports'
diff --git a/homeassistant/components/cups/manifest.json b/homeassistant/components/cups/manifest.json
new file mode 100644
index 00000000000..def2846c4ca
--- /dev/null
+++ b/homeassistant/components/cups/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "cups",
+ "name": "Cups",
+ "documentation": "https://www.home-assistant.io/components/cups",
+ "requirements": [
+ "pycups==1.9.73"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py
index 99dadcfe596..cf0ba5f7f8d 100644
--- a/homeassistant/components/cups/sensor.py
+++ b/homeassistant/components/cups/sensor.py
@@ -1,9 +1,4 @@
-"""
-Details about printers which are connected to CUPS.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.cups/
-"""
+"""Details about printers which are connected to CUPS."""
import importlib
import logging
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pycups==1.9.73']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DEVICE_URI = 'device_uri'
diff --git a/homeassistant/components/currencylayer/manifest.json b/homeassistant/components/currencylayer/manifest.json
new file mode 100644
index 00000000000..7064590bf25
--- /dev/null
+++ b/homeassistant/components/currencylayer/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "currencylayer",
+ "name": "Currencylayer",
+ "documentation": "https://www.home-assistant.io/components/currencylayer",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py
index 9b7186e8e09..bedd5f079ce 100644
--- a/homeassistant/components/currencylayer/sensor.py
+++ b/homeassistant/components/currencylayer/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for currencylayer.com exchange rates service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.currencylayer/
-"""
+"""Support for currencylayer.com exchange rates service."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/daikin/.translations/es.json b/homeassistant/components/daikin/.translations/es.json
new file mode 100644
index 00000000000..d3a733a3f9b
--- /dev/null
+++ b/homeassistant/components/daikin/.translations/es.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "El dispositivo ya est\u00e1 configurado",
+ "device_fail": "Error inesperado al crear el dispositivo.",
+ "device_timeout": "Tiempo de espera agotado al conectar con el dispositivo."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "host": "Host"
+ },
+ "description": "Introduce la IP de tu aire acondicionado Daikin",
+ "title": "Configurar aire acondicionado Daikin"
+ }
+ },
+ "title": "Aire acondicionado Daikin"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/daikin/.translations/ko.json b/homeassistant/components/daikin/.translations/ko.json
index a203affdbb8..2291d46800d 100644
--- a/homeassistant/components/daikin/.translations/ko.json
+++ b/homeassistant/components/daikin/.translations/ko.json
@@ -1,9 +1,9 @@
{
"config": {
"abort": {
- "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
- "device_fail": "\uc7a5\uce58\ub97c \uad6c\uc131\ud558\ub294\uc911 \uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
- "device_timeout": "\uc7a5\uce58 \uc5f0\uacb0 \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4."
+ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
+ "device_fail": "\uae30\uae30\ub97c \uad6c\uc131\ud558\ub294 \ub3c4\uc911 \uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
+ "device_timeout": "\uae30\uae30 \uc5f0\uacb0 \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4."
},
"step": {
"user": {
diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py
index 5ad21f5954f..fc15ebea772 100644
--- a/homeassistant/components/daikin/__init__.py
+++ b/homeassistant/components/daikin/__init__.py
@@ -2,13 +2,14 @@
import asyncio
from datetime import timedelta
import logging
-from socket import timeout
-import async_timeout
+from aiohttp import ClientConnectionError
+from async_timeout import timeout
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
-from homeassistant.const import CONF_HOSTS, CONF_HOST
+from homeassistant.const import CONF_HOST, CONF_HOSTS
+from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.typing import HomeAssistantType
@@ -16,8 +17,6 @@ from homeassistant.util import Throttle
from . import config_flow # noqa pylint_disable=unused-import
-REQUIREMENTS = ['pydaikin==1.3.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'daikin'
@@ -88,14 +87,17 @@ async def daikin_api_setup(hass, host):
from pydaikin.appliance import Appliance
session = hass.helpers.aiohttp_client.async_get_clientsession()
try:
- with async_timeout.timeout(10):
+ with timeout(10):
device = Appliance(host, session)
await device.init()
except asyncio.TimeoutError:
- _LOGGER.error("Connection to Daikin could not be established")
- return None
+ _LOGGER.debug("Connection to %s timed out", host)
+ raise ConfigEntryNotReady
+ except ClientConnectionError:
+ _LOGGER.debug("ClientConnectionError to %s", host)
+ raise ConfigEntryNotReady
except Exception: # pylint: disable=broad-except
- _LOGGER.error("Unexpected error creating device")
+ _LOGGER.error("Unexpected error creating device %s", host)
return None
api = DaikinApi(device)
@@ -119,7 +121,7 @@ class DaikinApi:
try:
await self.device.update_status()
self._available = True
- except timeout:
+ except ClientConnectionError:
_LOGGER.warning(
"Connection failed for %s", self.ip_address
)
diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py
index c42f2785576..f348c88daac 100644
--- a/homeassistant/components/daikin/climate.py
+++ b/homeassistant/components/daikin/climate.py
@@ -53,7 +53,8 @@ HA_ATTR_TO_DAIKIN = {
}
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up the Daikin HVAC platform.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py
index 3c5daac4653..7c214e77050 100644
--- a/homeassistant/components/daikin/config_flow.py
+++ b/homeassistant/components/daikin/config_flow.py
@@ -2,7 +2,8 @@
import asyncio
import logging
-import async_timeout
+from aiohttp import ClientError
+from async_timeout import timeout
import voluptuous as vol
from homeassistant import config_entries
@@ -42,10 +43,13 @@ class FlowHandler(config_entries.ConfigFlow):
host,
self.hass.helpers.aiohttp_client.async_get_clientsession(),
)
- with async_timeout.timeout(10):
+ with timeout(10):
await device.init()
except asyncio.TimeoutError:
return self.async_abort(reason='device_timeout')
+ except ClientError:
+ _LOGGER.exception("ClientError")
+ return self.async_abort(reason='device_fail')
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error creating device")
return self.async_abort(reason='device_fail')
diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json
new file mode 100644
index 00000000000..ab842950e24
--- /dev/null
+++ b/homeassistant/components/daikin/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "daikin",
+ "name": "Daikin",
+ "documentation": "https://www.home-assistant.io/components/daikin",
+ "requirements": [
+ "pydaikin==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fredrike",
+ "@rofrantz"
+ ]
+}
diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py
index 5a005e29989..c4f885f5081 100644
--- a/homeassistant/components/daikin/sensor.py
+++ b/homeassistant/components/daikin/sensor.py
@@ -13,7 +13,8 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up the Daikin sensors.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/daikin/switch.py b/homeassistant/components/daikin/switch.py
index 29159c60fe9..3106a7e8013 100644
--- a/homeassistant/components/daikin/switch.py
+++ b/homeassistant/components/daikin/switch.py
@@ -10,7 +10,8 @@ _LOGGER = logging.getLogger(__name__)
ZONE_ICON = 'mdi:home-circle'
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up the platform.
Can only be called when a user accidentally mentions the platform in their
@@ -26,7 +27,8 @@ async def async_setup_entry(hass, entry, async_add_entities):
if zones:
async_add_entities([
DaikinZoneSwitch(daikin_api, zone_id)
- for zone_id in range(len(zones))
+ for zone_id, name in enumerate(zones)
+ if name != '-'
])
diff --git a/homeassistant/components/danfoss_air/__init__.py b/homeassistant/components/danfoss_air/__init__.py
index f4a7b92c17c..a340b94e9a4 100644
--- a/homeassistant/components/danfoss_air/__init__.py
+++ b/homeassistant/components/danfoss_air/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['pydanfossair==0.0.7']
-
_LOGGER = logging.getLogger(__name__)
DANFOSS_AIR_PLATFORMS = ['sensor', 'binary_sensor', 'switch']
diff --git a/homeassistant/components/danfoss_air/manifest.json b/homeassistant/components/danfoss_air/manifest.json
new file mode 100644
index 00000000000..8af1707de65
--- /dev/null
+++ b/homeassistant/components/danfoss_air/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "danfoss_air",
+ "name": "Danfoss air",
+ "documentation": "https://www.home-assistant.io/components/danfoss_air",
+ "requirements": [
+ "pydanfossair==0.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/darksky/manifest.json b/homeassistant/components/darksky/manifest.json
new file mode 100644
index 00000000000..e4e6482484c
--- /dev/null
+++ b/homeassistant/components/darksky/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "darksky",
+ "name": "Darksky",
+ "documentation": "https://www.home-assistant.io/components/darksky",
+ "requirements": [
+ "python-forecastio==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py
index 540568b5785..63c2f782d17 100644
--- a/homeassistant/components/darksky/sensor.py
+++ b/homeassistant/components/darksky/sensor.py
@@ -1,27 +1,19 @@
-"""
-Support for Dark Sky weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.darksky/
-"""
-from datetime import timedelta
+"""Support for Dark Sky weather service."""
import logging
+from datetime import timedelta
+import voluptuous as vol
from requests.exceptions import (
ConnectionError as ConnectError, HTTPError, Timeout)
-import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE,
- CONF_MONITORED_CONDITIONS, CONF_NAME, UNIT_UV_INDEX, CONF_UPDATE_INTERVAL,
- CONF_SCAN_INTERVAL, CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
-import homeassistant.helpers.config_validation as cv
+ CONF_MONITORED_CONDITIONS, CONF_NAME, UNIT_UV_INDEX, CONF_SCAN_INTERVAL)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['python-forecastio==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Dark Sky"
@@ -163,47 +155,38 @@ CONDITION_PICTURES = {
# Language Supported Codes
LANGUAGE_CODES = [
- 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es',
- 'et', 'fi', 'fr', 'he', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'ka', 'ko',
- 'kw', 'lv', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv',
- 'tet', 'tr', 'uk', 'x-pig-latin', 'zh', 'zh-tw',
+ 'ar', 'az', 'be', 'bg', 'bn', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'en',
+ 'ja', 'ka', 'kn', 'ko', 'eo', 'es', 'et', 'fi', 'fr', 'he', 'hi', 'hr',
+ 'hu', 'id', 'is', 'it', 'kw', 'lv', 'ml', 'mr', 'nb', 'nl', 'pa', 'pl',
+ 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'ta', 'te', 'tet', 'tr', 'uk',
+ 'ur', 'x-pig-latin', 'zh', 'zh-tw',
]
ALLOWED_UNITS = ['auto', 'si', 'us', 'ca', 'uk', 'uk2']
-PLATFORM_SCHEMA = vol.All(
- PLATFORM_SCHEMA.extend({
- vol.Required(CONF_MONITORED_CONDITIONS):
- vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
- vol.Required(CONF_API_KEY): cv.string,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_UNITS): vol.In(ALLOWED_UNITS),
- vol.Optional(CONF_LANGUAGE,
- default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES),
- vol.Inclusive(
- CONF_LATITUDE,
- 'coordinates',
- 'Latitude and longitude must exist together'
- ): cv.latitude,
- vol.Inclusive(
- CONF_LONGITUDE,
- 'coordinates',
- 'Latitude and longitude must exist together'
- ): cv.longitude,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_FORECAST):
- vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]),
- vol.Optional(CONF_HOURLY_FORECAST):
- vol.All(cv.ensure_list, [vol.Range(min=0, max=48)]),
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=SCAN_INTERVAL
- )
-)
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_MONITORED_CONDITIONS):
+ vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
+ vol.Required(CONF_API_KEY): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_UNITS): vol.In(ALLOWED_UNITS),
+ vol.Optional(CONF_LANGUAGE,
+ default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES),
+ vol.Inclusive(
+ CONF_LATITUDE,
+ 'coordinates',
+ 'Latitude and longitude must exist together'
+ ): cv.latitude,
+ vol.Inclusive(
+ CONF_LONGITUDE,
+ 'coordinates',
+ 'Latitude and longitude must exist together'
+ ): cv.longitude,
+ vol.Optional(CONF_FORECAST):
+ vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]),
+ vol.Optional(CONF_HOURLY_FORECAST):
+ vol.All(cv.ensure_list, [vol.Range(min=0, max=48)]),
+})
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/darksky/weather.py b/homeassistant/components/darksky/weather.py
index 5b3db4312bf..dd945e7b01c 100644
--- a/homeassistant/components/darksky/weather.py
+++ b/homeassistant/components/darksky/weather.py
@@ -16,8 +16,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.util.pressure import convert as convert_pressure
-REQUIREMENTS = ['python-forecastio==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Dark Sky"
diff --git a/homeassistant/components/datadog/__init__.py b/homeassistant/components/datadog/__init__.py
index 3b519514d17..a59d828301c 100644
--- a/homeassistant/components/datadog/__init__.py
+++ b/homeassistant/components/datadog/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
from homeassistant.helpers import state as state_helper
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['datadog==0.15.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_RATE = 'rate'
diff --git a/homeassistant/components/datadog/manifest.json b/homeassistant/components/datadog/manifest.json
new file mode 100644
index 00000000000..40a2e82d53a
--- /dev/null
+++ b/homeassistant/components/datadog/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "datadog",
+ "name": "Datadog",
+ "documentation": "https://www.home-assistant.io/components/datadog",
+ "requirements": [
+ "datadog==0.15.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ddwrt/device_tracker.py b/homeassistant/components/ddwrt/device_tracker.py
index cf8c8e1779b..a97fe340f92 100644
--- a/homeassistant/components/ddwrt/device_tracker.py
+++ b/homeassistant/components/ddwrt/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for DD-WRT routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.ddwrt/
-"""
+"""Support for DD-WRT routers."""
import logging
import re
diff --git a/homeassistant/components/ddwrt/manifest.json b/homeassistant/components/ddwrt/manifest.json
new file mode 100644
index 00000000000..3c877a34841
--- /dev/null
+++ b/homeassistant/components/ddwrt/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "ddwrt",
+ "name": "Ddwrt",
+ "documentation": "https://www.home-assistant.io/components/ddwrt",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/deconz/.translations/ca.json b/homeassistant/components/deconz/.translations/ca.json
index 87189a93806..eebbb709a82 100644
--- a/homeassistant/components/deconz/.translations/ca.json
+++ b/homeassistant/components/deconz/.translations/ca.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "L'enlla\u00e7 ja est\u00e0 configurat",
"no_bridges": "No s'han descobert enlla\u00e7os amb deCONZ",
- "one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia deCONZ"
+ "one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia deCONZ",
+ "updated_instance": "S'ha actualitzat la inst\u00e0ncia de deCONZ amb una nova adre\u00e7a"
},
"error": {
"no_key": "No s'ha pogut obtenir una clau API"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Permet la importaci\u00f3 de sensors virtuals",
+ "allow_deconz_groups": "Permet la importaci\u00f3 de grups deCONZ"
+ },
+ "description": "Vols configurar Home Assistant per a que es connecti amb la passarel\u00b7la deCONZ proporcionada per l\u2019add-on {addon} de hass.io?",
+ "title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee (complement de Hass.io)"
+ },
"init": {
"data": {
"host": "Amfitri\u00f3",
diff --git a/homeassistant/components/deconz/.translations/de.json b/homeassistant/components/deconz/.translations/de.json
index 39975eaa39e..8ce199b4262 100644
--- a/homeassistant/components/deconz/.translations/de.json
+++ b/homeassistant/components/deconz/.translations/de.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "Bridge ist bereits konfiguriert",
"no_bridges": "Keine deCON-Bridges entdeckt",
- "one_instance_only": "Komponente unterst\u00fctzt nur eine deCONZ-Instanz"
+ "one_instance_only": "Komponente unterst\u00fctzt nur eine deCONZ-Instanz",
+ "updated_instance": "deCONZ-Instanz mit neuer Host-Adresse aktualisiert"
},
"error": {
"no_key": "Es konnte kein API-Schl\u00fcssel abgerufen werden"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Import virtueller Sensoren zulassen",
+ "allow_deconz_groups": "Import von deCONZ-Gruppen zulassen"
+ },
+ "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ gateway herstellt, der vom Add-on hass.io {addon} bereitgestellt wird?",
+ "title": "deCONZ Zigbee Gateway \u00fcber das Hass.io Add-on"
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/en.json b/homeassistant/components/deconz/.translations/en.json
index d8bcc95a115..981f579f09f 100644
--- a/homeassistant/components/deconz/.translations/en.json
+++ b/homeassistant/components/deconz/.translations/en.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "Bridge is already configured",
"no_bridges": "No deCONZ bridges discovered",
- "one_instance_only": "Component only supports one deCONZ instance"
+ "one_instance_only": "Component only supports one deCONZ instance",
+ "updated_instance": "Updated deCONZ instance with new host address"
},
"error": {
"no_key": "Couldn't get an API key"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Allow importing virtual sensors",
+ "allow_deconz_groups": "Allow importing deCONZ groups"
+ },
+ "description": "Do you want to configure Home Assistant to connect to the deCONZ gateway provided by the hass.io add-on {addon}?",
+ "title": "deCONZ Zigbee gateway via Hass.io add-on"
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/es-419.json b/homeassistant/components/deconz/.translations/es-419.json
index c2298a5fcc2..4ae633ef165 100644
--- a/homeassistant/components/deconz/.translations/es-419.json
+++ b/homeassistant/components/deconz/.translations/es-419.json
@@ -9,6 +9,12 @@
"no_key": "No se pudo obtener una clave de API"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Permitir la importaci\u00f3n de sensores virtuales",
+ "allow_deconz_groups": "Permitir la importaci\u00f3n de grupos deCONZ"
+ }
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/es.json b/homeassistant/components/deconz/.translations/es.json
index 34661f447d8..0c3284e74b3 100644
--- a/homeassistant/components/deconz/.translations/es.json
+++ b/homeassistant/components/deconz/.translations/es.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "El puente ya esta configurado",
"no_bridges": "No se han descubierto puentes deCONZ",
- "one_instance_only": "El componente s\u00f3lo soporta una instancia deCONZ"
+ "one_instance_only": "El componente s\u00f3lo soporta una instancia deCONZ",
+ "updated_instance": "Instancia deCONZ actualizada con nueva direcci\u00f3n de host"
},
"error": {
"no_key": "No se pudo obtener una clave API"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Permitir importar sensores virtuales",
+ "allow_deconz_groups": "Permite importar grupos de deCONZ"
+ },
+ "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de hass.io?",
+ "title": "Add-on deCONZ Zigbee v\u00eda Hass.io"
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/it.json b/homeassistant/components/deconz/.translations/it.json
index c0a23d47be3..dfff5743df7 100644
--- a/homeassistant/components/deconz/.translations/it.json
+++ b/homeassistant/components/deconz/.translations/it.json
@@ -9,6 +9,14 @@
"no_key": "Impossibile ottenere una API key"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Consenti l'importazione di sensori virtuali",
+ "allow_deconz_groups": "Consenti l'importazione di gruppi deCONZ"
+ },
+ "description": "Vuoi configurare Home Assistant per connettersi al gateway deCONZ fornito dal componente aggiuntivo hass.io {addon} ?",
+ "title": "Gateway Zigbee deCONZ tramite l'add-on Hass.io"
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/ko.json b/homeassistant/components/deconz/.translations/ko.json
index 6a527ab0a0b..f68b4dc10e9 100644
--- a/homeassistant/components/deconz/.translations/ko.json
+++ b/homeassistant/components/deconz/.translations/ko.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9bf\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4",
- "one_instance_only": "\uad6c\uc131\uc694\uc18c\ub294 \ud558\ub098\uc758 deCONZ \uc778\uc2a4\ud134\uc2a4\ub9cc \uc9c0\uc6d0\ud569\ub2c8\ub2e4"
+ "one_instance_only": "\uad6c\uc131\uc694\uc18c\ub294 \ud558\ub098\uc758 deCONZ \uc778\uc2a4\ud134\uc2a4\ub9cc \uc9c0\uc6d0\ud569\ub2c8\ub2e4",
+ "updated_instance": "deCONZ \uc778\uc2a4\ud134\uc2a4\ub97c \uc0c8\ub85c\uc6b4 \ud638\uc2a4\ud2b8 \uc8fc\uc18c\ub85c \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4"
},
"error": {
"no_key": "API \ud0a4\ub97c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "\uac00\uc0c1 \uc13c\uc11c \uac00\uc838\uc624\uae30 \ud5c8\uc6a9",
+ "allow_deconz_groups": "deCONZ \uadf8\ub8f9 \uac00\uc838\uc624\uae30 \ud5c8\uc6a9"
+ },
+ "description": "Hass.io \ubd80\uac00\uae30\ub2a5 {addon} \ub85c(\uc73c\ub85c) deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774"
+ },
"init": {
"data": {
"host": "\ud638\uc2a4\ud2b8",
@@ -25,7 +34,7 @@
"allow_clip_sensor": "\uac00\uc0c1 \uc13c\uc11c \uac00\uc838\uc624\uae30 \ud5c8\uc6a9",
"allow_deconz_groups": "deCONZ \uadf8\ub8f9 \uac00\uc838\uc624\uae30 \ud5c8\uc6a9"
},
- "title": "deCONZ\ub97c \uc704\ud55c \ucd94\uac00 \uad6c\uc131 \uc635\uc158"
+ "title": "deCONZ \ucd94\uac00 \uad6c\uc131 \uc635\uc158"
}
},
"title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774"
diff --git a/homeassistant/components/deconz/.translations/lb.json b/homeassistant/components/deconz/.translations/lb.json
index fc05bb8b94e..3308a557d5d 100644
--- a/homeassistant/components/deconz/.translations/lb.json
+++ b/homeassistant/components/deconz/.translations/lb.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "Bridge ass schon konfigur\u00e9iert",
"no_bridges": "Keng dECONZ bridges fonnt",
- "one_instance_only": "Komponent \u00ebnnerst\u00ebtzt n\u00ebmmen eng deCONZ Instanz"
+ "one_instance_only": "Komponent \u00ebnnerst\u00ebtzt n\u00ebmmen eng deCONZ Instanz",
+ "updated_instance": "deCONZ Instanz gouf mat der neier Adress vum Apparat ge\u00e4nnert"
},
"error": {
"no_key": "Konnt keen API Schl\u00ebssel kr\u00e9ien"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Erlaabt den Import vun virtuellen Sensoren",
+ "allow_deconz_groups": "Erlaabt den Import vun deCONZ Gruppen"
+ },
+ "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mat der deCONZ gateway ze verbannen d\u00e9i vum hass.io add-on {addon} bereet gestallt g\u00ebtt?",
+ "title": "deCONZ Zigbee gateway via Hass.io add-on"
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/nn.json b/homeassistant/components/deconz/.translations/nn.json
index 4bdc4b4c1be..46933ced427 100644
--- a/homeassistant/components/deconz/.translations/nn.json
+++ b/homeassistant/components/deconz/.translations/nn.json
@@ -23,7 +23,7 @@
"options": {
"data": {
"allow_clip_sensor": "Tillat importering av virtuelle sensorar",
- "allow_deconz_groups": "Tillat importering av deCONZ-grupper"
+ "allow_deconz_groups": "Tillat \u00e5 importera deCONZ-grupper"
},
"title": "Ekstra konfigurasjonsalternativ for deCONZ"
}
diff --git a/homeassistant/components/deconz/.translations/no.json b/homeassistant/components/deconz/.translations/no.json
index 1b0407e633d..59da47fc7ed 100644
--- a/homeassistant/components/deconz/.translations/no.json
+++ b/homeassistant/components/deconz/.translations/no.json
@@ -9,6 +9,14 @@
"no_key": "Kunne ikke f\u00e5 en API-n\u00f8kkel"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Tillat import av virtuelle sensorer",
+ "allow_deconz_groups": "Tillat import av deCONZ grupper"
+ },
+ "description": "\u00d8nsker du \u00e5 konfigurere Home Assistent for \u00e5 koble til deCONZ gateway gitt av Hass.io tillegget {addon}?",
+ "title": "deCONZ Zigbee gateway via Hass.io tillegg"
+ },
"init": {
"data": {
"host": "Vert",
diff --git a/homeassistant/components/deconz/.translations/pl.json b/homeassistant/components/deconz/.translations/pl.json
index 5a8b710c006..022a3284c14 100644
--- a/homeassistant/components/deconz/.translations/pl.json
+++ b/homeassistant/components/deconz/.translations/pl.json
@@ -9,6 +9,14 @@
"no_key": "Nie mo\u017cna uzyska\u0107 klucza API"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Zezwalaj na importowanie wirtualnych sensor\u00f3w",
+ "allow_deconz_groups": "Zezw\u00f3l na importowanie grup deCONZ"
+ },
+ "description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby po\u0142\u0105czy\u0142 si\u0119 z bramk\u0105 deCONZ dostarczon\u0105 przez dodatek hass.io {addon}?",
+ "title": "Bramka deCONZ Zigbee przez dodatek Hass.io"
+ },
"init": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/deconz/.translations/pt.json b/homeassistant/components/deconz/.translations/pt.json
index a0419b8baa4..47f5bb7db59 100644
--- a/homeassistant/components/deconz/.translations/pt.json
+++ b/homeassistant/components/deconz/.translations/pt.json
@@ -12,13 +12,13 @@
"init": {
"data": {
"host": "Servidor",
- "port": "Porta (por omiss\u00e3o: '80')"
+ "port": "Porta"
},
"title": "Defina o gateway deCONZ"
},
"link": {
"description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"",
- "title": "Link com deCONZ"
+ "title": "Liga\u00e7\u00e3o com deCONZ"
},
"options": {
"data": {
diff --git a/homeassistant/components/deconz/.translations/ru.json b/homeassistant/components/deconz/.translations/ru.json
index 5fd31ab9d8f..c81d2f8989e 100644
--- a/homeassistant/components/deconz/.translations/ru.json
+++ b/homeassistant/components/deconz/.translations/ru.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430",
"no_bridges": "\u0428\u043b\u044e\u0437\u044b deCONZ \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b",
- "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 deCONZ"
+ "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 deCONZ",
+ "updated_instance": "deCONZ \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d \u0441 \u043d\u043e\u0432\u044b\u043c \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0445\u043e\u0441\u0442\u0430"
},
"error": {
"no_key": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u043b\u044e\u0447 API"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432",
+ "allow_deconz_groups": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0433\u0440\u0443\u043f\u043f deCONZ"
+ },
+ "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 deCONZ (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?",
+ "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)"
+ },
"init": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
diff --git a/homeassistant/components/deconz/.translations/sl.json b/homeassistant/components/deconz/.translations/sl.json
index 686bb5b1e2e..ae9329c2857 100644
--- a/homeassistant/components/deconz/.translations/sl.json
+++ b/homeassistant/components/deconz/.translations/sl.json
@@ -9,6 +9,14 @@
"no_key": "Klju\u010da API ni mogo\u010de dobiti"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "Dovoli uvoz virtualnih senzorjev",
+ "allow_deconz_groups": "Dovoli uvoz deCONZ skupin"
+ },
+ "description": "\u017delite konfigurirati Home Assistant-a za povezavo z deCONZ prehodom, ki ga ponuja hass.io dodatek {addon} ?",
+ "title": "deCONZ Zigbee prehod preko dodatka Hass.io"
+ },
"init": {
"data": {
"host": "Gostitelj",
diff --git a/homeassistant/components/deconz/.translations/zh-Hant.json b/homeassistant/components/deconz/.translations/zh-Hant.json
index 524f68d41bc..06b174f27f5 100644
--- a/homeassistant/components/deconz/.translations/zh-Hant.json
+++ b/homeassistant/components/deconz/.translations/zh-Hant.json
@@ -3,12 +3,21 @@
"abort": {
"already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"no_bridges": "\u672a\u641c\u5c0b\u5230 deCONZ Bridfe",
- "one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u7269\u4ef6"
+ "one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u7269\u4ef6",
+ "updated_instance": "\u4f7f\u7528\u65b0\u4e3b\u6a5f\u7aef\u4f4d\u5740\u66f4\u65b0 deCONZ \u5be6\u4f8b"
},
"error": {
"no_key": "\u7121\u6cd5\u53d6\u5f97 API key"
},
"step": {
+ "hassio_confirm": {
+ "data": {
+ "allow_clip_sensor": "\u5141\u8a31\u532f\u5165\u865b\u64ec\u611f\u61c9\u5668",
+ "allow_deconz_groups": "\u5141\u8a31\u532f\u5165 deCONZ \u7fa4\u7d44"
+ },
+ "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u7d44\u4ef6 {addon} \u4e4b deCONZ \u9598\u9053\u5668\uff1f",
+ "title": "\u900f\u904e Hass.io \u9644\u52a0\u7d44\u4ef6 deCONZ Zigbee \u9598\u9053\u5668"
+ },
"init": {
"data": {
"host": "\u4e3b\u6a5f\u7aef",
diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py
index 8bdd946e2ef..153e654f3fb 100644
--- a/homeassistant/components/deconz/__init__.py
+++ b/homeassistant/components/deconz/__init__.py
@@ -5,15 +5,14 @@ from homeassistant import config_entries
from homeassistant.const import (
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
-from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
# Loading the config flow file will register the flow
-from .config_flow import configured_hosts
-from .const import DEFAULT_PORT, DOMAIN, _LOGGER
+from .config_flow import get_master_gateway
+from .const import (
+ CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
+ CONF_MASTER_GATEWAY, DEFAULT_PORT, DOMAIN, _LOGGER)
from .gateway import DeconzGateway
-REQUIREMENTS = ['pydeconz==54']
-
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_API_KEY): cv.string,
@@ -32,26 +31,27 @@ SERVICE_SCHEMA = vol.All(vol.Schema({
vol.Optional(SERVICE_ENTITY): cv.entity_id,
vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'),
vol.Required(SERVICE_DATA): dict,
+ vol.Optional(CONF_BRIDGEID): str
}), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD))
SERVICE_DEVICE_REFRESH = 'device_refresh'
+SERVICE_DEVICE_REFRESCH_SCHEMA = vol.All(vol.Schema({
+ vol.Optional(CONF_BRIDGEID): str
+}))
+
async def async_setup(hass, config):
"""Load configuration for deCONZ component.
Discovery has loaded the component if DOMAIN is not present in config.
"""
- if DOMAIN in config:
- deconz_config = None
- if CONF_HOST in config[DOMAIN]:
- deconz_config = config[DOMAIN]
- if deconz_config and not configured_hosts(hass):
- hass.async_add_job(hass.config_entries.flow.async_init(
- DOMAIN,
- context={'source': config_entries.SOURCE_IMPORT},
- data=deconz_config
- ))
+ if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
+ deconz_config = config[DOMAIN]
+ hass.async_create_task(hass.config_entries.flow.async_init(
+ DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
+ data=deconz_config
+ ))
return True
@@ -61,26 +61,20 @@ async def async_setup_entry(hass, config_entry):
Load config, group, light and sensor data for server information.
Start websocket for push notification of state changes from deCONZ.
"""
- if DOMAIN in hass.data:
- _LOGGER.error(
- "Config entry failed since one deCONZ instance already exists")
- return False
+ if DOMAIN not in hass.data:
+ hass.data[DOMAIN] = {}
+
+ if not config_entry.options:
+ await async_populate_options(hass, config_entry)
gateway = DeconzGateway(hass, config_entry)
if not await gateway.async_setup():
return False
- hass.data[DOMAIN] = gateway
+ hass.data[DOMAIN][gateway.bridgeid] = gateway
- device_registry = await \
- hass.helpers.device_registry.async_get_registry()
- device_registry.async_get_or_create(
- config_entry_id=config_entry.entry_id,
- connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)},
- identifiers={(DOMAIN, gateway.api.config.bridgeid)},
- manufacturer='Dresden Elektronik', model=gateway.api.config.modelid,
- name=gateway.api.config.name, sw_version=gateway.api.config.swversion)
+ await gateway.async_update_device_registry()
async def async_configure(call):
"""Set attribute of device in deCONZ.
@@ -100,8 +94,11 @@ async def async_setup_entry(hass, config_entry):
"""
field = call.data.get(SERVICE_FIELD, '')
entity_id = call.data.get(SERVICE_ENTITY)
- data = call.data.get(SERVICE_DATA)
- gateway = hass.data[DOMAIN]
+ data = call.data[SERVICE_DATA]
+
+ gateway = get_master_gateway(hass)
+ if CONF_BRIDGEID in call.data:
+ gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
if entity_id:
try:
@@ -117,7 +114,9 @@ async def async_setup_entry(hass, config_entry):
async def async_refresh_devices(call):
"""Refresh available devices from deCONZ."""
- gateway = hass.data[DOMAIN]
+ gateway = get_master_gateway(hass)
+ if CONF_BRIDGEID in call.data:
+ gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
groups = set(gateway.api.groups.keys())
lights = set(gateway.api.lights.keys())
@@ -151,7 +150,8 @@ async def async_setup_entry(hass, config_entry):
)
hass.services.async_register(
- DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)
+ DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices,
+ schema=SERVICE_DEVICE_REFRESCH_SCHEMA)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
return True
@@ -159,7 +159,33 @@ async def async_setup_entry(hass, config_entry):
async def async_unload_entry(hass, config_entry):
"""Unload deCONZ config entry."""
- gateway = hass.data.pop(DOMAIN)
- hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
- hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
+ gateway = hass.data[DOMAIN].pop(config_entry.data[CONF_BRIDGEID])
+
+ if not hass.data[DOMAIN]:
+ hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
+ hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
+ elif gateway.master:
+ await async_populate_options(hass, config_entry)
+ new_master_gateway = next(iter(hass.data[DOMAIN].values()))
+ await async_populate_options(hass, new_master_gateway.config_entry)
+
return await gateway.async_reset()
+
+
+async def async_populate_options(hass, config_entry):
+ """Populate default options for gateway.
+
+ Called by setup_entry and unload_entry.
+ Makes sure there is always one master available.
+ """
+ master = not get_master_gateway(hass)
+
+ options = {
+ CONF_MASTER_GATEWAY: master,
+ CONF_ALLOW_CLIP_SENSOR: config_entry.data.get(
+ CONF_ALLOW_CLIP_SENSOR, False),
+ CONF_ALLOW_DECONZ_GROUPS: config_entry.data.get(
+ CONF_ALLOW_DECONZ_GROUPS, True)
+ }
+
+ hass.config_entries.async_update_entry(config_entry, options=options)
diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py
index 2b0c2037248..fbb15abc744 100644
--- a/homeassistant/components/deconz/binary_sensor.py
+++ b/homeassistant/components/deconz/binary_sensor.py
@@ -4,12 +4,9 @@ from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from .const import (
- ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
- NEW_SENSOR)
+from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR
from .deconz_device import DeconzDevice
-
-DEPENDENCIES = ['deconz']
+from .gateway import get_gateway_from_config_entry
ATTR_ORIENTATION = 'orientation'
ATTR_TILTANGLE = 'tiltangle'
@@ -24,22 +21,26 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ binary sensor."""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_sensor(sensors):
"""Add binary sensor from deCONZ."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR
entities = []
- allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
+
for sensor in sensors:
+
if sensor.type in DECONZ_BINARY_SENSOR and \
- not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
+ not (not gateway.allow_clip_sensor and
+ sensor.type.startswith('CLIP')):
+
entities.append(DeconzBinarySensor(sensor, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor))
async_add_sensor(gateway.api.sensors.values())
diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py
index 1f39b8705c7..c4a021a80c2 100644
--- a/homeassistant/components/deconz/climate.py
+++ b/homeassistant/components/deconz/climate.py
@@ -7,12 +7,9 @@ from homeassistant.const import (
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from .const import (
- ATTR_OFFSET, ATTR_VALVE, CONF_ALLOW_CLIP_SENSOR,
- DOMAIN as DECONZ_DOMAIN, NEW_SENSOR)
+from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
from .deconz_device import DeconzDevice
-
-DEPENDENCIES = ['deconz']
+from .gateway import get_gateway_from_config_entry
async def async_setup_entry(hass, config_entry, async_add_entities):
@@ -20,22 +17,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Thermostats are based on the same device class as sensors in deCONZ.
"""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_climate(sensors):
"""Add climate devices from deCONZ."""
from pydeconz.sensor import THERMOSTAT
entities = []
- allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
+
for sensor in sensors:
+
if sensor.type in THERMOSTAT and \
- not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
+ not (not gateway.allow_clip_sensor and
+ sensor.type.startswith('CLIP')):
+
entities.append(DeconzThermostat(sensor, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_SENSOR, async_add_climate))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_SENSOR), async_add_climate))
async_add_climate(gateway.api.sensors.values())
diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py
index 38849fb37b3..d9065ad2727 100644
--- a/homeassistant/components/deconz/config_flow.py
+++ b/homeassistant/components/deconz/config_flow.py
@@ -9,17 +9,24 @@ from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
-from .const import (
- CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
- DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT,
- DOMAIN)
+from .const import CONF_BRIDGEID, DEFAULT_PORT, DOMAIN
+
+CONF_SERIAL = 'serial'
@callback
-def configured_hosts(hass):
- """Return a set of the configured hosts."""
- return set(entry.data[CONF_HOST] for entry
- in hass.config_entries.async_entries(DOMAIN))
+def configured_gateways(hass):
+ """Return a set of all configured gateways."""
+ return {entry.data[CONF_BRIDGEID]: entry for entry
+ in hass.config_entries.async_entries(DOMAIN)}
+
+
+@callback
+def get_master_gateway(hass):
+ """Return the gateway which is marked as master."""
+ for gateway in hass.data[DOMAIN].values():
+ if gateway.master:
+ return gateway
@config_entries.HANDLERS.register(DOMAIN)
@@ -36,19 +43,19 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
self.bridges = []
self.deconz_config = {}
+ async def async_step_init(self, user_input=None):
+ """Needed in order to not require re-translation of strings."""
+ return await self.async_step_user(user_input)
+
async def async_step_user(self, user_input=None):
"""Handle a deCONZ config flow start.
- Only allows one instance to be set up.
If only one bridge is found go to link step.
If more than one bridge is found let user choose bridge to link.
If no bridge is found allow user to manually input configuration.
"""
from pydeconz.utils import async_discovery
- if configured_hosts(self.hass):
- return self.async_abort(reason='one_instance_only')
-
if user_input is not None:
for bridge in self.bridges:
if bridge[CONF_HOST] == user_input[CONF_HOST]:
@@ -99,9 +106,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
errors = {}
if user_input is not None:
- if configured_hosts(self.hass):
- return self.async_abort(reason='one_instance_only')
-
session = aiohttp_client.async_get_clientsession(self.hass)
try:
@@ -114,62 +118,57 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
else:
self.deconz_config[CONF_API_KEY] = api_key
- return await self.async_step_options()
+ return await self._create_entry()
return self.async_show_form(
step_id='link',
errors=errors,
)
- async def async_step_options(self, user_input=None):
- """Extra options for deCONZ.
-
- CONF_CLIP_SENSOR -- Allow user to choose if they want clip sensors.
- CONF_DECONZ_GROUPS -- Allow user to choose if they want deCONZ groups.
- """
+ async def _create_entry(self):
+ """Create entry for gateway."""
from pydeconz.utils import async_get_bridgeid
- if user_input is not None:
- self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = \
- user_input[CONF_ALLOW_CLIP_SENSOR]
- self.deconz_config[CONF_ALLOW_DECONZ_GROUPS] = \
- user_input[CONF_ALLOW_DECONZ_GROUPS]
+ if CONF_BRIDGEID not in self.deconz_config:
+ session = aiohttp_client.async_get_clientsession(self.hass)
- if CONF_BRIDGEID not in self.deconz_config:
- session = aiohttp_client.async_get_clientsession(self.hass)
- try:
- with async_timeout.timeout(10):
- self.deconz_config[CONF_BRIDGEID] = \
- await async_get_bridgeid(
- session, **self.deconz_config)
+ try:
+ with async_timeout.timeout(10):
+ self.deconz_config[CONF_BRIDGEID] = \
+ await async_get_bridgeid(
+ session, **self.deconz_config)
- except asyncio.TimeoutError:
- return self.async_abort(reason='no_bridges')
+ except asyncio.TimeoutError:
+ return self.async_abort(reason='no_bridges')
- return self.async_create_entry(
- title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
- data=self.deconz_config
- )
-
- return self.async_show_form(
- step_id='options',
- data_schema=vol.Schema({
- vol.Optional(CONF_ALLOW_CLIP_SENSOR,
- default=DEFAULT_ALLOW_CLIP_SENSOR): bool,
- vol.Optional(CONF_ALLOW_DECONZ_GROUPS,
- default=DEFAULT_ALLOW_DECONZ_GROUPS): bool,
- }),
+ return self.async_create_entry(
+ title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
+ data=self.deconz_config
)
+ async def _update_entry(self, entry, host):
+ """Update existing entry."""
+ entry.data[CONF_HOST] = host
+ self.hass.config_entries.async_update_entry(entry)
+
async def async_step_discovery(self, discovery_info):
"""Prepare configuration for a discovered deCONZ bridge.
This flow is triggered by the discovery component.
"""
- deconz_config = {}
- deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
- deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
- deconz_config[CONF_BRIDGEID] = discovery_info.get('serial')
+ bridgeid = discovery_info[CONF_SERIAL]
+ gateway_entries = configured_gateways(self.hass)
+
+ if bridgeid in gateway_entries:
+ entry = gateway_entries[bridgeid]
+ await self._update_entry(entry, discovery_info[CONF_HOST])
+ return self.async_abort(reason='updated_instance')
+
+ deconz_config = {
+ CONF_HOST: discovery_info[CONF_HOST],
+ CONF_PORT: discovery_info[CONF_PORT],
+ CONF_BRIDGEID: discovery_info[CONF_SERIAL]
+ }
return await self.async_step_import(deconz_config)
@@ -186,24 +185,24 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
Otherwise we will delegate to `link` step which
will ask user to link the bridge.
"""
- if configured_hosts(self.hass):
- return self.async_abort(reason='one_instance_only')
-
self.deconz_config = import_config
if CONF_API_KEY not in import_config:
return await self.async_step_link()
- user_input = {CONF_ALLOW_CLIP_SENSOR: True,
- CONF_ALLOW_DECONZ_GROUPS: True}
- return await self.async_step_options(user_input=user_input)
+ return await self._create_entry()
async def async_step_hassio(self, user_input=None):
"""Prepare configuration for a Hass.io deCONZ bridge.
This flow is triggered by the discovery component.
"""
- if configured_hosts(self.hass):
- return self.async_abort(reason='one_instance_only')
+ bridgeid = user_input[CONF_SERIAL]
+ gateway_entries = configured_gateways(self.hass)
+
+ if bridgeid in gateway_entries:
+ entry = gateway_entries[bridgeid]
+ await self._update_entry(entry, user_input[CONF_HOST])
+ return self.async_abort(reason='updated_instance')
self._hassio_discovery = user_input
@@ -212,29 +211,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
async def async_step_hassio_confirm(self, user_input=None):
"""Confirm a Hass.io discovery."""
if user_input is not None:
- data = self._hassio_discovery
+ self.deconz_config = {
+ CONF_HOST: self._hassio_discovery[CONF_HOST],
+ CONF_PORT: self._hassio_discovery[CONF_PORT],
+ CONF_BRIDGEID: self._hassio_discovery[CONF_SERIAL],
+ CONF_API_KEY: self._hassio_discovery[CONF_API_KEY]
+ }
- return self.async_create_entry(
- title=data['addon'], data={
- CONF_HOST: data[CONF_HOST],
- CONF_PORT: data[CONF_PORT],
- CONF_BRIDGEID: data['serial'],
- CONF_API_KEY: data[CONF_API_KEY],
- CONF_ALLOW_CLIP_SENSOR:
- user_input[CONF_ALLOW_CLIP_SENSOR],
- CONF_ALLOW_DECONZ_GROUPS:
- user_input[CONF_ALLOW_DECONZ_GROUPS],
- })
+ return await self._create_entry()
return self.async_show_form(
step_id='hassio_confirm',
description_placeholders={
'addon': self._hassio_discovery['addon']
- },
- data_schema=vol.Schema({
- vol.Optional(CONF_ALLOW_CLIP_SENSOR,
- default=DEFAULT_ALLOW_CLIP_SENSOR): bool,
- vol.Optional(CONF_ALLOW_DECONZ_GROUPS,
- default=DEFAULT_ALLOW_DECONZ_GROUPS): bool,
- })
+ }
)
diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py
index b26fddd9147..bf0f5884073 100644
--- a/homeassistant/components/deconz/const.py
+++ b/homeassistant/components/deconz/const.py
@@ -12,22 +12,21 @@ DEFAULT_ALLOW_DECONZ_GROUPS = False
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
CONF_BRIDGEID = 'bridgeid'
+CONF_MASTER_GATEWAY = 'master'
SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover',
'light', 'scene', 'sensor', 'switch']
-DECONZ_REACHABLE = 'deconz_reachable'
-
-NEW_GROUP = 'deconz_new_group'
-NEW_LIGHT = 'deconz_new_light'
-NEW_SCENE = 'deconz_new_scene'
-NEW_SENSOR = 'deconz_new_sensor'
+NEW_GROUP = 'group'
+NEW_LIGHT = 'light'
+NEW_SCENE = 'scene'
+NEW_SENSOR = 'sensor'
NEW_DEVICE = {
- 'group': NEW_GROUP,
- 'light': NEW_LIGHT,
- 'scene': NEW_SCENE,
- 'sensor': NEW_SENSOR
+ NEW_GROUP: 'deconz_new_group_{}',
+ NEW_LIGHT: 'deconz_new_light_{}',
+ NEW_SCENE: 'deconz_new_scene_{}',
+ NEW_SENSOR: 'deconz_new_sensor_{}'
}
ATTR_DARK = 'dark'
diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py
index fda4fe4309c..45a1b0c67e5 100644
--- a/homeassistant/components/deconz/cover.py
+++ b/homeassistant/components/deconz/cover.py
@@ -5,11 +5,9 @@ from homeassistant.components.cover import (
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from .const import (
- COVER_TYPES, DAMPERS, DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, WINDOW_COVERS)
+from .const import COVER_TYPES, DAMPERS, NEW_LIGHT, WINDOW_COVERS
from .deconz_device import DeconzDevice
-
-DEPENDENCIES = ['deconz']
+from .gateway import get_gateway_from_config_entry
ZIGBEE_SPEC = ['lumi.curtain']
@@ -25,22 +23,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Covers are based on same device class as lights in deCONZ.
"""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_cover(lights):
"""Add cover from deCONZ."""
entities = []
+
for light in lights:
+
if light.type in COVER_TYPES:
if light.modelid in ZIGBEE_SPEC:
entities.append(DeconzCoverZigbeeSpec(light, gateway))
+
else:
entities.append(DeconzCover(light, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_LIGHT, async_add_cover))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_LIGHT), async_add_cover))
async_add_cover(gateway.api.lights.values())
diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py
index bfcbd158b9f..6923c93dd6f 100644
--- a/homeassistant/components/deconz/deconz_device.py
+++ b/homeassistant/components/deconz/deconz_device.py
@@ -4,7 +4,7 @@ from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
-from .const import DECONZ_REACHABLE, DOMAIN as DECONZ_DOMAIN
+from .const import DOMAIN as DECONZ_DOMAIN
class DeconzDevice(Entity):
@@ -21,14 +21,14 @@ class DeconzDevice(Entity):
self._device.register_async_callback(self.async_update_callback)
self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id
self.unsub_dispatcher = async_dispatcher_connect(
- self.hass, DECONZ_REACHABLE, self.async_update_callback)
+ self.hass, self.gateway.event_reachable,
+ self.async_update_callback)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed."""
- if self.unsub_dispatcher is not None:
- self.unsub_dispatcher()
self._device.remove_callback(self.async_update_callback)
- self._device = None
+ del self.gateway.deconz_ids[self.entity_id]
+ self.unsub_dispatcher()
@callback
def async_update_callback(self, reason):
diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py
index 11fb247a6f4..46078ea6648 100644
--- a/homeassistant/components/deconz/gateway.py
+++ b/homeassistant/components/deconz/gateway.py
@@ -6,16 +6,23 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.const import CONF_EVENT, CONF_HOST, CONF_ID
from homeassistant.core import EventOrigin, callback
from homeassistant.helpers import aiohttp_client
+from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
from homeassistant.util import slugify
from .const import (
- _LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, NEW_DEVICE, NEW_SENSOR,
- SUPPORTED_PLATFORMS)
+ _LOGGER, CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
+ CONF_MASTER_GATEWAY, DOMAIN, NEW_DEVICE, NEW_SENSOR, SUPPORTED_PLATFORMS)
from .errors import AuthenticationRequired, CannotConnect
+@callback
+def get_gateway_from_config_entry(hass, config_entry):
+ """Return gateway with a matching bridge id."""
+ return hass.data[DOMAIN][config_entry.data[CONF_BRIDGEID]]
+
+
class DeconzGateway:
"""Manages a single deCONZ gateway."""
@@ -30,6 +37,40 @@ class DeconzGateway:
self.events = []
self.listeners = []
+ @property
+ def bridgeid(self) -> str:
+ """Return the unique identifier of the gateway."""
+ return self.config_entry.data[CONF_BRIDGEID]
+
+ @property
+ def master(self) -> bool:
+ """Gateway which is used with deCONZ services without defining id."""
+ return self.config_entry.options[CONF_MASTER_GATEWAY]
+
+ @property
+ def allow_clip_sensor(self) -> bool:
+ """Allow loading clip sensor from gateway."""
+ return self.config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
+
+ @property
+ def allow_deconz_groups(self) -> bool:
+ """Allow loading deCONZ groups from gateway."""
+ return self.config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True)
+
+ async def async_update_device_registry(self):
+ """Update device registry."""
+ device_registry = await \
+ self.hass.helpers.device_registry.async_get_registry()
+ device_registry.async_get_or_create(
+ config_entry_id=self.config_entry.entry_id,
+ connections={(CONNECTION_NETWORK_MAC, self.api.config.mac)},
+ identifiers={(DOMAIN, self.api.config.bridgeid)},
+ manufacturer='Dresden Elektronik',
+ model=self.api.config.modelid,
+ name=self.api.config.name,
+ sw_version=self.api.config.swversion
+ )
+
async def async_setup(self):
"""Set up a deCONZ gateway."""
hass = self.hass
@@ -52,39 +93,63 @@ class DeconzGateway:
hass.config_entries.async_forward_entry_setup(
self.config_entry, component))
- self.listeners.append(
- async_dispatcher_connect(
- hass, NEW_SENSOR, self.async_add_remote))
+ self.listeners.append(async_dispatcher_connect(
+ hass, self.async_event_new_device(NEW_SENSOR),
+ self.async_add_remote))
self.async_add_remote(self.api.sensors.values())
self.api.start()
+ self.config_entry.add_update_listener(self.async_new_address_callback)
+
return True
+ @staticmethod
+ async def async_new_address_callback(hass, entry):
+ """Handle signals of gateway getting new address.
+
+ This is a static method because a class method (bound method),
+ can not be used with weak references.
+ """
+ gateway = hass.data[DOMAIN][entry.data[CONF_BRIDGEID]]
+ gateway.api.close()
+ gateway.api.host = entry.data[CONF_HOST]
+ gateway.api.start()
+
+ @property
+ def event_reachable(self):
+ """Gateway specific event to signal a change in connection status."""
+ return 'deconz_reachable_{}'.format(self.bridgeid)
+
@callback
def async_connection_status_callback(self, available):
"""Handle signals of gateway connection status."""
self.available = available
- async_dispatcher_send(
- self.hass, DECONZ_REACHABLE, {'state': True, 'attr': 'reachable'})
+ async_dispatcher_send(self.hass, self.event_reachable,
+ {'state': True, 'attr': 'reachable'})
+
+ @callback
+ def async_event_new_device(self, device_type):
+ """Gateway specific event to signal new device."""
+ return NEW_DEVICE[device_type].format(self.bridgeid)
@callback
def async_add_device_callback(self, device_type, device):
"""Handle event of new device creation in deCONZ."""
if not isinstance(device, list):
device = [device]
- async_dispatcher_send(self.hass, NEW_DEVICE[device_type], device)
+ async_dispatcher_send(
+ self.hass, self.async_event_new_device(device_type), device)
@callback
def async_add_remote(self, sensors):
"""Set up remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
- allow_clip_sensor = self.config_entry.data.get(
- CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_REMOTE and \
- not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
+ not (not self.allow_clip_sensor and
+ sensor.type.startswith('CLIP')):
self.events.append(DeconzEvent(self.hass, sensor))
@callback
diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py
index 3b63da8d9f8..7514162fefa 100644
--- a/homeassistant/components/deconz/light.py
+++ b/homeassistant/components/deconz/light.py
@@ -8,12 +8,9 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util
-from .const import (
- CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DECONZ_DOMAIN, COVER_TYPES, NEW_GROUP,
- NEW_LIGHT, SWITCH_TYPES)
+from .const import COVER_TYPES, NEW_GROUP, NEW_LIGHT, SWITCH_TYPES
from .deconz_device import DeconzDevice
-
-DEPENDENCIES = ['deconz']
+from .gateway import get_gateway_from_config_entry
async def async_setup_platform(
@@ -24,32 +21,35 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ lights and groups from a config entry."""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_light(lights):
"""Add light from deCONZ."""
entities = []
+
for light in lights:
if light.type not in COVER_TYPES + SWITCH_TYPES:
entities.append(DeconzLight(light, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_LIGHT, async_add_light))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_LIGHT), async_add_light))
@callback
def async_add_group(groups):
"""Add group from deCONZ."""
entities = []
- allow_group = config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True)
+
for group in groups:
- if group.lights and allow_group:
+ if group.lights and gateway.allow_deconz_groups:
entities.append(DeconzLight(group, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_GROUP, async_add_group))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_GROUP), async_add_group))
async_add_light(gateway.api.lights.values())
async_add_group(gateway.api.groups.values())
diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json
new file mode 100644
index 00000000000..c68da4b566c
--- /dev/null
+++ b/homeassistant/components/deconz/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "deconz",
+ "name": "Deconz",
+ "documentation": "https://www.home-assistant.io/components/deconz",
+ "requirements": [
+ "pydeconz==54"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@kane610"
+ ]
+}
diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py
index 22b4c47f2ab..d2e7f6719e9 100644
--- a/homeassistant/components/deconz/scene.py
+++ b/homeassistant/components/deconz/scene.py
@@ -3,9 +3,8 @@ from homeassistant.components.scene import Scene
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from .const import DOMAIN as DECONZ_DOMAIN, NEW_SCENE
-
-DEPENDENCIES = ['deconz']
+from .const import NEW_SCENE
+from .gateway import get_gateway_from_config_entry
async def async_setup_platform(
@@ -16,17 +15,20 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up scenes for deCONZ component."""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_scene(scenes):
"""Add scene from deCONZ."""
entities = []
+
for scene in scenes:
entities.append(DeconzScene(scene, gateway))
+
async_add_entities(entities)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_SCENE, async_add_scene))
+
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_SCENE), async_add_scene))
async_add_scene(gateway.api.scenes.values())
diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py
index e6b033906e7..9f1e87db4ba 100644
--- a/homeassistant/components/deconz/sensor.py
+++ b/homeassistant/components/deconz/sensor.py
@@ -5,12 +5,9 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import slugify
-from .const import (
- ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
- NEW_SENSOR)
+from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR
from .deconz_device import DeconzDevice
-
-DEPENDENCIES = ['deconz']
+from .gateway import get_gateway_from_config_entry
ATTR_CURRENT = 'current'
ATTR_DAYLIGHT = 'daylight'
@@ -25,7 +22,7 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ sensors."""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_sensor(sensors):
@@ -33,19 +30,24 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
from pydeconz.sensor import (
DECONZ_SENSOR, SWITCH as DECONZ_REMOTE)
entities = []
- allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
+
for sensor in sensors:
+
if sensor.type in DECONZ_SENSOR and \
- not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
+ not (not gateway.allow_clip_sensor and
+ sensor.type.startswith('CLIP')):
+
if sensor.type in DECONZ_REMOTE:
if sensor.battery:
entities.append(DeconzBattery(sensor, gateway))
+
else:
entities.append(DeconzSensor(sensor, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor))
async_add_sensor(gateway.api.sensors.values())
diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml
index cde7ac79f4c..4d77101cf0d 100644
--- a/homeassistant/components/deconz/services.yaml
+++ b/homeassistant/components/deconz/services.yaml
@@ -13,6 +13,13 @@ configure:
data:
description: Data is a json object with what data you want to alter.
example: '{"on": true}'
+ bridgeid:
+ description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name.
+ example: '00212EFFFF012345'
device_refresh:
- description: Refresh device lists from deCONZ.
\ No newline at end of file
+ description: Refresh device lists from deCONZ.
+ fields:
+ bridgeid:
+ description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name.
+ example: '00212EFFFF012345'
diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json
index d0ae34e7c7a..16177dbd3cc 100644
--- a/homeassistant/components/deconz/strings.json
+++ b/homeassistant/components/deconz/strings.json
@@ -35,6 +35,7 @@
"abort": {
"already_configured": "Bridge is already configured",
"no_bridges": "No deCONZ bridges discovered",
+ "updated_instance": "Updated deCONZ instance with new host address",
"one_instance_only": "Component only supports one deCONZ instance"
}
}
diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py
index 56d37d504cb..c399f5da128 100644
--- a/homeassistant/components/deconz/switch.py
+++ b/homeassistant/components/deconz/switch.py
@@ -3,10 +3,9 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from .const import DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, POWER_PLUGS, SIRENS
+from .const import NEW_LIGHT, POWER_PLUGS, SIRENS
from .deconz_device import DeconzDevice
-
-DEPENDENCIES = ['deconz']
+from .gateway import get_gateway_from_config_entry
async def async_setup_platform(
@@ -20,21 +19,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Switches are based same device class as lights in deCONZ.
"""
- gateway = hass.data[DECONZ_DOMAIN]
+ gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_switch(lights):
"""Add switch from deCONZ."""
entities = []
+
for light in lights:
+
if light.type in POWER_PLUGS:
entities.append(DeconzPowerPlug(light, gateway))
+
elif light.type in SIRENS:
entities.append(DeconzSiren(light, gateway))
+
async_add_entities(entities, True)
- gateway.listeners.append(
- async_dispatcher_connect(hass, NEW_LIGHT, async_add_switch))
+ gateway.listeners.append(async_dispatcher_connect(
+ hass, gateway.async_event_new_device(NEW_LIGHT), async_add_switch))
async_add_switch(gateway.api.lights.values())
diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py
index 7c3274cf83b..2f6c050b79e 100644
--- a/homeassistant/components/decora/light.py
+++ b/homeassistant/components/decora/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Decora dimmers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.decora/
-"""
+"""Support for Decora dimmers."""
import importlib
import logging
from functools import wraps
@@ -17,8 +12,6 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['decora==0.6', 'bluepy==1.1.4']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_DECORA_LED = (SUPPORT_BRIGHTNESS)
diff --git a/homeassistant/components/decora/manifest.json b/homeassistant/components/decora/manifest.json
new file mode 100644
index 00000000000..923a543e827
--- /dev/null
+++ b/homeassistant/components/decora/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "decora",
+ "name": "Decora",
+ "documentation": "https://www.home-assistant.io/components/decora",
+ "requirements": [
+ "bluepy==1.1.4",
+ "decora==0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py
index b9c575dbd5a..390af765b62 100644
--- a/homeassistant/components/decora_wifi/light.py
+++ b/homeassistant/components/decora_wifi/light.py
@@ -1,12 +1,4 @@
-"""
-Interfaces with the myLeviton API for Decora Smart WiFi products.
-
-See:
-http://www.leviton.com/en/products/lighting-controls/decora-smart-with-wifi
-
-Uses Leviton's cloud services API for cloud-to-cloud integration.
-
-"""
+"""Interfaces with the myLeviton API for Decora Smart WiFi products."""
import logging
@@ -20,8 +12,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['decora_wifi==1.3']
-
_LOGGER = logging.getLogger(__name__)
# Validation of the user's configuration
diff --git a/homeassistant/components/decora_wifi/manifest.json b/homeassistant/components/decora_wifi/manifest.json
new file mode 100644
index 00000000000..42ab6bfd6c1
--- /dev/null
+++ b/homeassistant/components/decora_wifi/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "decora_wifi",
+ "name": "Decora wifi",
+ "documentation": "https://www.home-assistant.io/components/decora_wifi",
+ "requirements": [
+ "decora_wifi==1.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py
index 6743893888d..23add299b2f 100644
--- a/homeassistant/components/default_config/__init__.py
+++ b/homeassistant/components/default_config/__init__.py
@@ -4,29 +4,14 @@ try:
except ImportError:
av = None
+from homeassistant.setup import async_setup_component
+
DOMAIN = 'default_config'
-DEPENDENCIES = [
- 'automation',
- 'cloud',
- 'config',
- 'conversation',
- 'frontend',
- 'history',
- 'logbook',
- 'map',
- 'mobile_app',
- 'person',
- 'script',
- 'sun',
- 'system_health',
- 'updater',
- 'zeroconf',
-]
-# Only automatically set up the stream component when dependency installed
-if av is not None:
- DEPENDENCIES.append('stream')
async def async_setup(hass, config):
"""Initialize default configuration."""
- return True
+ if av is None:
+ return True
+
+ return await async_setup_component(hass, 'stream', config)
diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json
new file mode 100644
index 00000000000..f52da35dc64
--- /dev/null
+++ b/homeassistant/components/default_config/manifest.json
@@ -0,0 +1,24 @@
+{
+ "domain": "default_config",
+ "name": "Default config",
+ "documentation": "https://www.home-assistant.io/components/default_config",
+ "requirements": [],
+ "dependencies": [
+ "automation",
+ "cloud",
+ "config",
+ "conversation",
+ "frontend",
+ "history",
+ "logbook",
+ "map",
+ "mobile_app",
+ "person",
+ "script",
+ "sun",
+ "system_health",
+ "updater",
+ "zeroconf"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/deluge/manifest.json b/homeassistant/components/deluge/manifest.json
new file mode 100644
index 00000000000..2b3c6d4c055
--- /dev/null
+++ b/homeassistant/components/deluge/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "deluge",
+ "name": "Deluge",
+ "documentation": "https://www.home-assistant.io/components/deluge",
+ "requirements": [
+ "deluge-client==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/deluge/sensor.py b/homeassistant/components/deluge/sensor.py
index f56b3ac4b97..1002ae51077 100644
--- a/homeassistant/components/deluge/sensor.py
+++ b/homeassistant/components/deluge/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring the Deluge BitTorrent client API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.deluge/
-"""
+"""Support for monitoring the Deluge BitTorrent client API."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.exceptions import PlatformNotReady
-REQUIREMENTS = ['deluge-client==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
_THROTTLED_REFRESH = None
diff --git a/homeassistant/components/deluge/switch.py b/homeassistant/components/deluge/switch.py
index 0ece742aa03..d72ce9a5308 100644
--- a/homeassistant/components/deluge/switch.py
+++ b/homeassistant/components/deluge/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for setting the Deluge BitTorrent client in Pause.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.deluge/
-"""
+"""Support for setting the Deluge BitTorrent client in Pause."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['deluge-client==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Deluge Switch'
diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py
index 2699ade0b1d..c61673afda6 100644
--- a/homeassistant/components/demo/__init__.py
+++ b/homeassistant/components/demo/__init__.py
@@ -1,14 +1,14 @@
"""Set up the demo environment that mimics interaction with devices."""
import asyncio
+import logging
import time
from homeassistant import bootstrap
import homeassistant.core as ha
-from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
+from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START
-DEPENDENCIES = ['conversation', 'introduction', 'zone']
DOMAIN = 'demo'
-
+_LOGGER = logging.getLogger(__name__)
COMPONENTS_WITH_DEMO_PLATFORM = [
'air_quality',
'alarm_control_panel',
@@ -33,17 +33,19 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
async def async_setup(hass, config):
"""Set up the demo environment."""
- group = hass.components.group
- configurator = hass.components.configurator
- persistent_notification = hass.components.persistent_notification
+ if DOMAIN not in config:
+ return True
config.setdefault(ha.DOMAIN, {})
config.setdefault(DOMAIN, {})
- if config[DOMAIN].get('hide_demo_state') != 1:
- hass.states.async_set('a.Demo_Mode', 'Enabled')
+ # Set up demo platforms
+ for component in COMPONENTS_WITH_DEMO_PLATFORM:
+ hass.async_create_task(hass.helpers.discovery.async_load_platform(
+ component, DOMAIN, {}, config,
+ ))
- # Setup sun
+ # Set up sun
if not hass.config.latitude:
hass.config.latitude = 32.87336
@@ -51,16 +53,9 @@ async def async_setup(hass, config):
hass.config.longitude = 117.22743
tasks = [
- bootstrap.async_setup_component(hass, 'sun')
+ bootstrap.async_setup_component(hass, 'sun', config)
]
- # Set up demo platforms
- demo_config = config.copy()
- for component in COMPONENTS_WITH_DEMO_PLATFORM:
- demo_config[component] = {CONF_PLATFORM: 'demo'}
- tasks.append(
- bootstrap.async_setup_component(hass, component, demo_config))
-
# Set up input select
tasks.append(bootstrap.async_setup_component(
hass, 'input_select',
@@ -72,6 +67,7 @@ async def async_setup(hass, config):
'initial': 'Anne Therese',
'name': 'Cook today',
'options': ['Paulus', 'Anne Therese']}}}))
+
# Set up input boolean
tasks.append(bootstrap.async_setup_component(
hass, 'input_boolean',
@@ -96,25 +92,60 @@ async def async_setup(hass, config):
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}}))
- results = await asyncio.gather(*tasks, loop=hass.loop)
+ results = await asyncio.gather(*tasks)
if any(not result for result in results):
return False
# Set up example persistent notification
- persistent_notification.async_create(
+ hass.components.persistent_notification.async_create(
'This is an example of a persistent notification.',
title='Example Notification')
- # Set up room groups
+ # Set up configurator
+ configurator_ids = []
+ configurator = hass.components.configurator
+
+ def hue_configuration_callback(data):
+ """Fake callback, mark config as done."""
+ time.sleep(2)
+
+ # First time it is called, pretend it failed.
+ if len(configurator_ids) == 1:
+ configurator.notify_errors(
+ configurator_ids[0],
+ "Failed to register, please try again.")
+
+ configurator_ids.append(0)
+ else:
+ configurator.request_done(configurator_ids[0])
+
+ request_id = configurator.async_request_config(
+ "Philips Hue", hue_configuration_callback,
+ description=("Press the button on the bridge to register Philips "
+ "Hue with Home Assistant."),
+ description_image="/static/images/config_philips_hue.jpg",
+ fields=[{'id': 'username', 'name': 'Username'}],
+ submit_caption="I have pressed the button"
+ )
+ configurator_ids.append(request_id)
+
+ async def demo_start_listener(_event):
+ """Finish set up."""
+ await finish_setup(hass, config)
+
+ hass.bus.async_listen(EVENT_HOMEASSISTANT_START, demo_start_listener)
+
+ return True
+
+
+async def finish_setup(hass, config):
+ """Finish set up once demo platforms are set up."""
lights = sorted(hass.states.async_entity_ids('light'))
switches = sorted(hass.states.async_entity_ids('switch'))
- media_players = sorted(hass.states.async_entity_ids('media_player'))
-
- tasks2 = []
# Set up history graph
- tasks2.append(bootstrap.async_setup_component(
+ await bootstrap.async_setup_component(
hass, 'history_graph',
{'history_graph': {'switches': {
'name': 'Recent Switches',
@@ -122,10 +153,10 @@ async def async_setup(hass, config):
'hours_to_show': 1,
'refresh': 60
}}}
- ))
+ )
# Set up scripts
- tasks2.append(bootstrap.async_setup_component(
+ await bootstrap.async_setup_component(
hass, 'script',
{'script': {
'demo': {
@@ -144,10 +175,10 @@ async def async_setup(hass, config):
'service': 'light.turn_off',
'data': {ATTR_ENTITY_ID: lights[0]}
}]
- }}}))
+ }}})
# Set up scenes
- tasks2.append(bootstrap.async_setup_component(
+ await bootstrap.async_setup_component(
hass, 'scene',
{'scene': [
{'name': 'Romantic lights',
@@ -161,66 +192,4 @@ async def async_setup(hass, config):
switches[0]: True,
switches[1]: False,
}},
- ]}))
-
- tasks2.append(group.Group.async_create_group(hass, 'Living Room', [
- lights[1], switches[0], 'input_select.living_room_preset',
- 'cover.living_room_window', media_players[1],
- 'scene.romantic_lights']))
- tasks2.append(group.Group.async_create_group(hass, 'Bedroom', [
- lights[0], switches[1], media_players[0],
- 'input_number.noise_allowance']))
- tasks2.append(group.Group.async_create_group(hass, 'Kitchen', [
- lights[2], 'cover.kitchen_window', 'lock.kitchen_door']))
- tasks2.append(group.Group.async_create_group(hass, 'Doors', [
- 'lock.front_door', 'lock.kitchen_door',
- 'garage_door.right_garage_door', 'garage_door.left_garage_door']))
- tasks2.append(group.Group.async_create_group(hass, 'Automations', [
- 'input_select.who_cooks', 'input_boolean.notify', ]))
- tasks2.append(group.Group.async_create_group(hass, 'People', [
- 'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
- 'device_tracker.demo_paulus']))
- tasks2.append(group.Group.async_create_group(hass, 'Downstairs', [
- 'group.living_room', 'group.kitchen',
- 'scene.romantic_lights', 'cover.kitchen_window',
- 'cover.living_room_window', 'group.doors',
- 'climate.ecobee',
- ], view=True))
-
- results = await asyncio.gather(*tasks2, loop=hass.loop)
-
- if any(not result for result in results):
- return False
-
- # Set up configurator
- configurator_ids = []
-
- def hue_configuration_callback(data):
- """Fake callback, mark config as done."""
- time.sleep(2)
-
- # First time it is called, pretend it failed.
- if len(configurator_ids) == 1:
- configurator.notify_errors(
- configurator_ids[0],
- "Failed to register, please try again.")
-
- configurator_ids.append(0)
- else:
- configurator.request_done(configurator_ids[0])
-
- def setup_configurator():
- """Set up a configurator."""
- request_id = configurator.request_config(
- "Philips Hue", hue_configuration_callback,
- description=("Press the button on the bridge to register Philips "
- "Hue with Home Assistant."),
- description_image="/static/images/config_philips_hue.jpg",
- fields=[{'id': 'username', 'name': 'Username'}],
- submit_caption="I have pressed the button"
- )
- configurator_ids.append(request_id)
-
- hass.async_add_job(setup_configurator)
-
- return True
+ ]})
diff --git a/homeassistant/components/demo/air_quality.py b/homeassistant/components/demo/air_quality.py
index b2b9c10574f..77e5c0b2b1a 100644
--- a/homeassistant/components/demo/air_quality.py
+++ b/homeassistant/components/demo/air_quality.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that offers fake air quality data.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that offers fake air quality data."""
from homeassistant.components.air_quality import AirQualityEntity
diff --git a/homeassistant/components/demo/alarm_control_panel.py b/homeassistant/components/demo/alarm_control_panel.py
index 4d317f52daa..3cf5aaca57e 100644
--- a/homeassistant/components/demo/alarm_control_panel.py
+++ b/homeassistant/components/demo/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that has two fake alarm control panels.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that has two fake alarm control panels."""
import datetime
from homeassistant.components.manual.alarm_control_panel import ManualAlarm
from homeassistant.const import (
diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py
index d656b79e8ed..437497e4fac 100644
--- a/homeassistant/components/demo/binary_sensor.py
+++ b/homeassistant/components/demo/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that has two fake binary sensors.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that has two fake binary sensors."""
from homeassistant.components.binary_sensor import BinarySensorDevice
diff --git a/homeassistant/components/demo/calendar.py b/homeassistant/components/demo/calendar.py
index 720b4cc5180..6096f8247c4 100644
--- a/homeassistant/components/demo/calendar.py
+++ b/homeassistant/components/demo/calendar.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that has two fake binary sensors.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that has two fake binary sensors."""
import copy
from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME
diff --git a/homeassistant/components/demo/camera.py b/homeassistant/components/demo/camera.py
index 34a0894ac60..95c7df58200 100644
--- a/homeassistant/components/demo/camera.py
+++ b/homeassistant/components/demo/camera.py
@@ -1,9 +1,4 @@
-"""
-Demo camera platform that has a fake camera.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo camera platform that has a fake camera."""
import logging
import os
diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py
index b1dd1b0ba45..70eed0c3616 100644
--- a/homeassistant/components/demo/climate.py
+++ b/homeassistant/components/demo/climate.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that offers a fake climate device.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that offers a fake climate device."""
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.climate import ClimateDevice
diff --git a/homeassistant/components/demo/cover.py b/homeassistant/components/demo/cover.py
index ddcf07fd5e5..aa2931a987a 100644
--- a/homeassistant/components/demo/cover.py
+++ b/homeassistant/components/demo/cover.py
@@ -1,9 +1,4 @@
-"""
-Demo platform for the cover component.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform for the cover component."""
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.components.cover import (
diff --git a/homeassistant/components/demo/device_tracker.py b/homeassistant/components/demo/device_tracker.py
index 608fc560cf9..ff038d7009e 100644
--- a/homeassistant/components/demo/device_tracker.py
+++ b/homeassistant/components/demo/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Demo platform for the Device tracker component.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform for the Device tracker component."""
import random
from homeassistant.components.device_tracker import DOMAIN
diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py
index 53729795f71..4710bbecfe1 100644
--- a/homeassistant/components/demo/fan.py
+++ b/homeassistant/components/demo/fan.py
@@ -1,9 +1,4 @@
-"""
-Demo fan platform that has a fake fan.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo fan platform that has a fake fan."""
from homeassistant.const import STATE_OFF
from homeassistant.components.fan import (
diff --git a/homeassistant/components/demo/image_processing.py b/homeassistant/components/demo/image_processing.py
index 71ec2dccbc6..acb97e4ebd6 100644
--- a/homeassistant/components/demo/image_processing.py
+++ b/homeassistant/components/demo/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Support for the demo image processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/demo/
-"""
+"""Support for the demo image processing."""
from homeassistant.components.image_processing import (
ImageProcessingFaceEntity, ATTR_CONFIDENCE, ATTR_NAME, ATTR_AGE,
ATTR_GENDER
diff --git a/homeassistant/components/demo/light.py b/homeassistant/components/demo/light.py
index a5b22108e81..285866c6eb8 100644
--- a/homeassistant/components/demo/light.py
+++ b/homeassistant/components/demo/light.py
@@ -1,9 +1,4 @@
-"""
-Demo light platform that implements lights.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo light platform that implements lights."""
import random
from homeassistant.components.light import (
diff --git a/homeassistant/components/demo/lock.py b/homeassistant/components/demo/lock.py
index 03935c4f603..cd15a434138 100644
--- a/homeassistant/components/demo/lock.py
+++ b/homeassistant/components/demo/lock.py
@@ -1,9 +1,4 @@
-"""
-Demo lock platform that has two fake locks.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo lock platform that has two fake locks."""
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
from homeassistant.components.lock import SUPPORT_OPEN, LockDevice
diff --git a/homeassistant/components/demo/manifest.json b/homeassistant/components/demo/manifest.json
new file mode 100644
index 00000000000..4f167ecae25
--- /dev/null
+++ b/homeassistant/components/demo/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "demo",
+ "name": "Demo",
+ "documentation": "https://www.home-assistant.io/components/demo",
+ "requirements": [],
+ "dependencies": [
+ "conversation",
+ "zone",
+ "group",
+ "configurator"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py
index 33d2b98d225..cb3f3b5b46a 100644
--- a/homeassistant/components/demo/media_player.py
+++ b/homeassistant/components/demo/media_player.py
@@ -1,19 +1,13 @@
-"""
-Demo implementation of the media player.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
-from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
-import homeassistant.util.dt as dt_util
-
+"""Demo implementation of the media player."""
from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import (
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
- SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE,
- SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF,
- SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
+ SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
+ SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET,
+ SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
+from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
+import homeassistant.util.dt as dt_util
def setup_platform(hass, config, add_entities, discovery_info=None):
@@ -35,7 +29,8 @@ DEFAULT_SOUND_MODE = 'Dummy Music'
YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \
- SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOUND_MODE | SUPPORT_SELECT_SOURCE
+ SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOUND_MODE | SUPPORT_SELECT_SOURCE | \
+ SUPPORT_SEEK
MUSIC_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
diff --git a/homeassistant/components/demo/notify.py b/homeassistant/components/demo/notify.py
index 5b8e1f1688f..92aaea6882d 100644
--- a/homeassistant/components/demo/notify.py
+++ b/homeassistant/components/demo/notify.py
@@ -1,9 +1,4 @@
-"""
-Demo notification service.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo notification service."""
from homeassistant.components.notify import BaseNotificationService
EVENT_NOTIFY = "notify"
diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py
index f44061ac8f9..b28330fdc67 100644
--- a/homeassistant/components/demo/remote.py
+++ b/homeassistant/components/demo/remote.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that has two fake remotes.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that has two fake remotes."""
from homeassistant.components.remote import RemoteDevice
from homeassistant.const import DEVICE_DEFAULT_NAME
diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py
index 7921181b742..ea35c729517 100644
--- a/homeassistant/components/demo/sensor.py
+++ b/homeassistant/components/demo/sensor.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that has a couple of fake sensors.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that has a couple of fake sensors."""
from homeassistant.const import (
ATTR_BATTERY_LEVEL, TEMP_CELSIUS, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE)
diff --git a/homeassistant/components/demo/services.yaml b/homeassistant/components/demo/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/demo/switch.py b/homeassistant/components/demo/switch.py
index 0ac2011a6dc..e7a3b1741a2 100644
--- a/homeassistant/components/demo/switch.py
+++ b/homeassistant/components/demo/switch.py
@@ -1,9 +1,4 @@
-"""
-Demo platform that has two fake switches.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform that has two fake switches."""
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import DEVICE_DEFAULT_NAME
@@ -19,12 +14,13 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
class DemoSwitch(SwitchDevice):
"""Representation of a demo switch."""
- def __init__(self, name, state, icon, assumed):
+ def __init__(self, name, state, icon, assumed, device_class=None):
"""Initialize the Demo switch."""
self._name = name or DEVICE_DEFAULT_NAME
self._state = state
self._icon = icon
self._assumed = assumed
+ self._device_class = device_class
@property
def should_poll(self):
@@ -62,6 +58,11 @@ class DemoSwitch(SwitchDevice):
"""Return true if switch is on."""
return self._state
+ @property
+ def device_class(self):
+ """Return device of entity."""
+ return self._device_class
+
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._state = True
diff --git a/homeassistant/components/demo/tts.py b/homeassistant/components/demo/tts.py
index 1498472ef9f..bf18bc1630f 100644
--- a/homeassistant/components/demo/tts.py
+++ b/homeassistant/components/demo/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the demo speech service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/demo/
-"""
+"""Support for the demo speech service."""
import os
import voluptuous as vol
diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py
index 5ec7030f56c..dfb9c4e943e 100644
--- a/homeassistant/components/demo/vacuum.py
+++ b/homeassistant/components/demo/vacuum.py
@@ -1,9 +1,4 @@
-"""
-Demo platform for the vacuum component.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/demo/
-"""
+"""Demo platform for the vacuum component."""
import logging
from homeassistant.components.vacuum import (
diff --git a/homeassistant/components/denon/manifest.json b/homeassistant/components/denon/manifest.json
new file mode 100644
index 00000000000..2068b72fa9d
--- /dev/null
+++ b/homeassistant/components/denon/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "denon",
+ "name": "Denon",
+ "documentation": "https://www.home-assistant.io/components/denon",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py
index 3dc4e550d9b..07f6fcc7f9c 100644
--- a/homeassistant/components/denon/media_player.py
+++ b/homeassistant/components/denon/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Denon Network Receivers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.denon/
-"""
+"""Support for Denon Network Receivers."""
import logging
import telnetlib
diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json
new file mode 100644
index 00000000000..df7d58169e0
--- /dev/null
+++ b/homeassistant/components/denonavr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "denonavr",
+ "name": "Denonavr",
+ "documentation": "https://www.home-assistant.io/components/denonavr",
+ "requirements": [
+ "denonavr==0.7.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py
index 380484add53..da416ce8045 100644
--- a/homeassistant/components/denonavr/media_player.py
+++ b/homeassistant/components/denonavr/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Denon AVR receivers using their HTTP interface.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.denon/
-"""
+"""Support for Denon AVR receivers using their HTTP interface."""
from collections import namedtuple
import logging
@@ -23,8 +18,6 @@ from homeassistant.const import (
STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['denonavr==0.7.8']
-
_LOGGER = logging.getLogger(__name__)
ATTR_SOUND_MODE_RAW = 'sound_mode_raw'
diff --git a/homeassistant/components/deutsche_bahn/manifest.json b/homeassistant/components/deutsche_bahn/manifest.json
new file mode 100644
index 00000000000..463c7d03fbb
--- /dev/null
+++ b/homeassistant/components/deutsche_bahn/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "deutsche_bahn",
+ "name": "Deutsche bahn",
+ "documentation": "https://www.home-assistant.io/components/deutsche_bahn",
+ "requirements": [
+ "schiene==0.23"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py
index 41584b2561f..9c7518eb8ef 100644
--- a/homeassistant/components/deutsche_bahn/sensor.py
+++ b/homeassistant/components/deutsche_bahn/sensor.py
@@ -9,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['schiene==0.23']
-
_LOGGER = logging.getLogger(__name__)
CONF_DESTINATION = 'to'
diff --git a/homeassistant/components/device_sun_light_trigger/__init__.py b/homeassistant/components/device_sun_light_trigger/__init__.py
index 00adefc6b5c..945f8368671 100644
--- a/homeassistant/components/device_sun_light_trigger/__init__.py
+++ b/homeassistant/components/device_sun_light_trigger/__init__.py
@@ -17,8 +17,6 @@ from homeassistant.helpers.sun import is_up, get_astral_event_next
import homeassistant.helpers.config_validation as cv
DOMAIN = 'device_sun_light_trigger'
-DEPENDENCIES = ['light', 'device_tracker', 'group']
-
CONF_DEVICE_GROUP = 'device_group'
CONF_DISABLE_TURN_OFF = 'disable_turn_off'
CONF_LIGHT_GROUP = 'light_group'
diff --git a/homeassistant/components/device_sun_light_trigger/manifest.json b/homeassistant/components/device_sun_light_trigger/manifest.json
new file mode 100644
index 00000000000..abe5a1d500c
--- /dev/null
+++ b/homeassistant/components/device_sun_light_trigger/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "device_sun_light_trigger",
+ "name": "Device sun light trigger",
+ "documentation": "https://www.home-assistant.io/components/device_sun_light_trigger",
+ "requirements": [],
+ "dependencies": [
+ "device_tracker",
+ "group",
+ "light"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/device_sun_light_trigger/services.yaml b/homeassistant/components/device_sun_light_trigger/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py
index 1263811aae7..60dac103a46 100644
--- a/homeassistant/components/device_tracker/__init__.py
+++ b/homeassistant/components/device_tracker/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provide functionality to keep track of devices.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/device_tracker/
-"""
+"""Provide functionality to keep track of devices."""
import asyncio
from datetime import timedelta
import logging
@@ -40,8 +35,6 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'device_tracker'
-DEPENDENCIES = ['zone', 'group']
-
GROUP_NAME_ALL_DEVICES = 'all devices'
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
diff --git a/homeassistant/components/device_tracker/manifest.json b/homeassistant/components/device_tracker/manifest.json
new file mode 100644
index 00000000000..7b32f7845a6
--- /dev/null
+++ b/homeassistant/components/device_tracker/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "device_tracker",
+ "name": "Device tracker",
+ "documentation": "https://www.home-assistant.io/components/device_tracker",
+ "requirements": [],
+ "dependencies": [
+ "group",
+ "zone"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/device_tracker/services.yaml b/homeassistant/components/device_tracker/services.yaml
index 7436bbd6ea4..938e9c8e324 100644
--- a/homeassistant/components/device_tracker/services.yaml
+++ b/homeassistant/components/device_tracker/services.yaml
@@ -25,40 +25,39 @@ see:
description: Battery level of device.
example: '100'
-icloud:
- icloud_lost_iphone:
- description: Service to play the lost iphone sound on an iDevice.
- fields:
- account_name:
- description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
- example: 'bart'
- device_name:
- description: Name of the device that will play the sound. This is optional, if it isn't given it will play on all devices for the given account.
- example: 'iphonebart'
- icloud_set_interval:
- description: Service to set the interval of an iDevice.
- fields:
- account_name:
- description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
- example: 'bart'
- device_name:
- description: Name of the device that will get a new interval. This is optional, if it isn't given it will change the interval for all devices for the given account.
- example: 'iphonebart'
- interval:
- description: The interval (in minutes) that the iDevice will have until the according device_tracker entity changes from zone or until this service is used again. This is optional, if it isn't given the interval of the device will revert back to the original interval based on the current state.
- example: 1
- icloud_update:
- description: Service to ask for an update of an iDevice.
- fields:
- account_name:
- description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
- example: 'bart'
- device_name:
- description: Name of the device that will be updated. This is optional, if it isn't given it will update all devices for the given account.
- example: 'iphonebart'
- icloud_reset_account:
- description: Service to restart an iCloud account. Helpful when not all devices are found after initializing or when you add a new device.
- fields:
- account_name:
- description: Name of the account in the config that will be restarted. This is optional, if it isn't given it will restart all accounts.
- example: 'bart'
+icloud_lost_iphone:
+ description: Service to play the lost iphone sound on an iDevice.
+ fields:
+ account_name:
+ description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
+ example: 'bart'
+ device_name:
+ description: Name of the device that will play the sound. This is optional, if it isn't given it will play on all devices for the given account.
+ example: 'iphonebart'
+icloud_set_interval:
+ description: Service to set the interval of an iDevice.
+ fields:
+ account_name:
+ description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
+ example: 'bart'
+ device_name:
+ description: Name of the device that will get a new interval. This is optional, if it isn't given it will change the interval for all devices for the given account.
+ example: 'iphonebart'
+ interval:
+ description: The interval (in minutes) that the iDevice will have until the according device_tracker entity changes from zone or until this service is used again. This is optional, if it isn't given the interval of the device will revert back to the original interval based on the current state.
+ example: 1
+icloud_update:
+ description: Service to ask for an update of an iDevice.
+ fields:
+ account_name:
+ description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
+ example: 'bart'
+ device_name:
+ description: Name of the device that will be updated. This is optional, if it isn't given it will update all devices for the given account.
+ example: 'iphonebart'
+icloud_reset_account:
+ description: Service to restart an iCloud account. Helpful when not all devices are found after initializing or when you add a new device.
+ fields:
+ account_name:
+ description: Name of the account in the config that will be restarted. This is optional, if it isn't given it will restart all accounts.
+ example: 'bart'
diff --git a/homeassistant/components/dht/manifest.json b/homeassistant/components/dht/manifest.json
new file mode 100644
index 00000000000..05889bdd326
--- /dev/null
+++ b/homeassistant/components/dht/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dht",
+ "name": "Dht",
+ "documentation": "https://www.home-assistant.io/components/dht",
+ "requirements": [
+ "Adafruit-DHT==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py
index 04c084784c7..d544bfa74e8 100644
--- a/homeassistant/components/dht/sensor.py
+++ b/homeassistant/components/dht/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Adafruit DHT temperature and humidity sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.dht/
-"""
+"""Support for Adafruit DHT temperature and humidity sensor."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from homeassistant.util.temperature import celsius_to_fahrenheit
-REQUIREMENTS = ['Adafruit-DHT==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_PIN = 'pin'
diff --git a/homeassistant/components/dialogflow/.translations/fr.json b/homeassistant/components/dialogflow/.translations/fr.json
new file mode 100644
index 00000000000..53edb21b8e8
--- /dev/null
+++ b/homeassistant/components/dialogflow/.translations/fr.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Votre instance de Home Assistant doit \u00eatre accessible depuis Internet pour recevoir les messages Dialogflow.",
+ "one_instance_allowed": "Une seule instance est n\u00e9cessaire."
+ },
+ "create_entry": {
+ "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer [Webhooks avec Mailgun] ( {mailgun_url} ). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / json \n\n Voir [la documentation] ( {docs_url} ) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes."
+ },
+ "step": {
+ "user": {
+ "description": "\u00cates-vous s\u00fbr de vouloir configurer Dialogflow?",
+ "title": "Configurer le Webhook Dialogflow"
+ }
+ },
+ "title": "Dialogflow"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/dialogflow/.translations/ru.json b/homeassistant/components/dialogflow/.translations/ru.json
index 899f776c095..d8b7db09a78 100644
--- a/homeassistant/components/dialogflow/.translations/ru.json
+++ b/homeassistant/components/dialogflow/.translations/ru.json
@@ -10,7 +10,7 @@
"step": {
"user": {
"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 Dialogflow?",
- "title": "Dialogflow Webhook"
+ "title": "Dialogflow"
}
},
"title": "Dialogflow"
diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py
index 210aebe80d5..a6134d4b19c 100644
--- a/homeassistant/components/dialogflow/__init__.py
+++ b/homeassistant/components/dialogflow/__init__.py
@@ -10,7 +10,6 @@ from homeassistant.helpers import intent, template, config_entry_flow
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['webhook']
DOMAIN = 'dialogflow'
SOURCE = "Home Assistant Dialogflow"
@@ -79,6 +78,11 @@ async def async_unload_entry(hass, entry):
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'Dialogflow Webhook',
diff --git a/homeassistant/components/dialogflow/manifest.json b/homeassistant/components/dialogflow/manifest.json
new file mode 100644
index 00000000000..d136b8a984d
--- /dev/null
+++ b/homeassistant/components/dialogflow/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dialogflow",
+ "name": "Dialogflow",
+ "documentation": "https://www.home-assistant.io/components/dialogflow",
+ "requirements": [],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/digital_ocean/__init__.py b/homeassistant/components/digital_ocean/__init__.py
index 7975a6eea0d..9e034b2428d 100644
--- a/homeassistant/components/digital_ocean/__init__.py
+++ b/homeassistant/components/digital_ocean/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-digitalocean==1.13.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CREATED_AT = 'created_at'
diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py
index d496a09161b..83406247a07 100644
--- a/homeassistant/components/digital_ocean/binary_sensor.py
+++ b/homeassistant/components/digital_ocean/binary_sensor.py
@@ -17,8 +17,6 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_DEVICE_CLASS = 'moving'
-DEPENDENCIES = ['digital_ocean']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]),
})
diff --git a/homeassistant/components/digital_ocean/manifest.json b/homeassistant/components/digital_ocean/manifest.json
new file mode 100644
index 00000000000..2ef940f60bd
--- /dev/null
+++ b/homeassistant/components/digital_ocean/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "digital_ocean",
+ "name": "Digital ocean",
+ "documentation": "https://www.home-assistant.io/components/digital_ocean",
+ "requirements": [
+ "python-digitalocean==1.13.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py
index bc4a6a29b42..8016ccef0ea 100644
--- a/homeassistant/components/digital_ocean/switch.py
+++ b/homeassistant/components/digital_ocean/switch.py
@@ -14,8 +14,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['digital_ocean']
-
DEFAULT_NAME = 'Droplet'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/digitalloggers/manifest.json b/homeassistant/components/digitalloggers/manifest.json
new file mode 100644
index 00000000000..990b39b21a5
--- /dev/null
+++ b/homeassistant/components/digitalloggers/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "digitalloggers",
+ "name": "Digitalloggers",
+ "documentation": "https://www.home-assistant.io/components/digitalloggers",
+ "requirements": [
+ "dlipower==0.7.165"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/digitalloggers/switch.py b/homeassistant/components/digitalloggers/switch.py
index 7bb2be19899..4d1a87c44f9 100644
--- a/homeassistant/components/digitalloggers/switch.py
+++ b/homeassistant/components/digitalloggers/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Digital Loggers DIN III Relays.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.digitalloggers/
-"""
+"""Support for Digital Loggers DIN III Relays."""
import logging
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['dlipower==0.7.165']
-
_LOGGER = logging.getLogger(__name__)
CONF_CYCLETIME = 'cycletime'
diff --git a/homeassistant/components/directv/manifest.json b/homeassistant/components/directv/manifest.json
new file mode 100644
index 00000000000..7dbe6122ac1
--- /dev/null
+++ b/homeassistant/components/directv/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "directv",
+ "name": "Directv",
+ "documentation": "https://www.home-assistant.io/components/directv",
+ "requirements": [
+ "directpy==0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py
index 9c5a3bf07b8..aaffd44d572 100644
--- a/homeassistant/components/directv/media_player.py
+++ b/homeassistant/components/directv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for the DirecTV receivers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.directv/
-"""
+"""Support for the DirecTV receivers."""
import logging
import requests
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['directpy==0.5']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MEDIA_CURRENTLY_RECORDING = 'media_currently_recording'
diff --git a/homeassistant/components/discogs/manifest.json b/homeassistant/components/discogs/manifest.json
new file mode 100644
index 00000000000..ca304bce88b
--- /dev/null
+++ b/homeassistant/components/discogs/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "discogs",
+ "name": "Discogs",
+ "documentation": "https://www.home-assistant.io/components/discogs",
+ "requirements": [
+ "discogs_client==2.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@thibmaek"
+ ]
+}
diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py
index 8cdc89a540e..f9f821668f9 100644
--- a/homeassistant/components/discogs/sensor.py
+++ b/homeassistant/components/discogs/sensor.py
@@ -1,9 +1,4 @@
-"""
-Show the amount of records in a user's Discogs collection.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.discogs/
-"""
+"""Show the amount of records in a user's Discogs collection."""
from datetime import timedelta
import logging
import random
@@ -17,8 +12,6 @@ from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['discogs_client==2.2.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_IDENTITY = 'identity'
diff --git a/homeassistant/components/discord/manifest.json b/homeassistant/components/discord/manifest.json
new file mode 100644
index 00000000000..155e2b6806f
--- /dev/null
+++ b/homeassistant/components/discord/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "discord",
+ "name": "Discord",
+ "documentation": "https://www.home-assistant.io/components/discord",
+ "requirements": [
+ "discord.py==0.16.12"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py
index d73382d8bcf..faf79d14e33 100644
--- a/homeassistant/components/discord/notify.py
+++ b/homeassistant/components/discord/notify.py
@@ -1,9 +1,4 @@
-"""
-Discord platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.discord/
-"""
+"""Discord platform for notify component."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET,
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['discord.py==0.16.12']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOKEN): cv.string
})
diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py
index ecbbe7ea5e0..7490b530926 100644
--- a/homeassistant/components/discovery/__init__.py
+++ b/homeassistant/components/discovery/__init__.py
@@ -20,8 +20,6 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['netdisco==2.5.0']
-
DOMAIN = 'discovery'
SCAN_INTERVAL = timedelta(seconds=300)
@@ -35,6 +33,7 @@ SERVICE_FREEBOX = 'freebox'
SERVICE_HASS_IOS_APP = 'hass_ios'
SERVICE_HASSIO = 'hassio'
SERVICE_HOMEKIT = 'homekit'
+SERVICE_HEOS = 'heos'
SERVICE_HUE = 'philips_hue'
SERVICE_IGD = 'igd'
SERVICE_IKEA_TRADFRI = 'ikea_tradfri'
@@ -57,6 +56,7 @@ CONFIG_ENTRY_HANDLERS = {
SERVICE_DECONZ: 'deconz',
'esphome': 'esphome',
'google_cast': 'cast',
+ SERVICE_HEOS: 'heos',
SERVICE_HUE: 'hue',
SERVICE_TELLDUSLIVE: 'tellduslive',
SERVICE_IKEA_TRADFRI: 'tradfri',
diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json
new file mode 100644
index 00000000000..845e1af15d4
--- /dev/null
+++ b/homeassistant/components/discovery/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "discovery",
+ "name": "Discovery",
+ "documentation": "https://www.home-assistant.io/components/discovery",
+ "requirements": [
+ "netdisco==2.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dlib_face_detect/image_processing.py b/homeassistant/components/dlib_face_detect/image_processing.py
index fea756395e4..0bc657a615d 100644
--- a/homeassistant/components/dlib_face_detect/image_processing.py
+++ b/homeassistant/components/dlib_face_detect/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will help set the Dlib face detect processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.dlib_face_detect/
-"""
+"""Component that will help set the Dlib face detect processing."""
import logging
import io
@@ -13,8 +8,6 @@ from homeassistant.components.image_processing import PLATFORM_SCHEMA # noqa
from homeassistant.components.image_processing import (
ImageProcessingFaceEntity, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME)
-REQUIREMENTS = ['face_recognition==1.2.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_LOCATION = 'location'
diff --git a/homeassistant/components/dlib_face_detect/manifest.json b/homeassistant/components/dlib_face_detect/manifest.json
new file mode 100644
index 00000000000..c2ede62ee5b
--- /dev/null
+++ b/homeassistant/components/dlib_face_detect/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dlib_face_detect",
+ "name": "Dlib face detect",
+ "documentation": "https://www.home-assistant.io/components/dlib_face_detect",
+ "requirements": [
+ "face_recognition==1.2.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dlib_face_identify/image_processing.py b/homeassistant/components/dlib_face_identify/image_processing.py
index 6611fb0edfb..569b1ecece2 100644
--- a/homeassistant/components/dlib_face_identify/image_processing.py
+++ b/homeassistant/components/dlib_face_identify/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will help set the Dlib face detect processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.dlib_face_identify/
-"""
+"""Component that will help set the Dlib face detect processing."""
import logging
import io
@@ -15,8 +10,6 @@ from homeassistant.components.image_processing import (
CONF_NAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['face_recognition==1.2.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_NAME = 'name'
diff --git a/homeassistant/components/dlib_face_identify/manifest.json b/homeassistant/components/dlib_face_identify/manifest.json
new file mode 100644
index 00000000000..388017f78bb
--- /dev/null
+++ b/homeassistant/components/dlib_face_identify/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dlib_face_identify",
+ "name": "Dlib face identify",
+ "documentation": "https://www.home-assistant.io/components/dlib_face_identify",
+ "requirements": [
+ "face_recognition==1.2.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dlink/manifest.json b/homeassistant/components/dlink/manifest.json
new file mode 100644
index 00000000000..8f7d07eb0db
--- /dev/null
+++ b/homeassistant/components/dlink/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dlink",
+ "name": "Dlink",
+ "documentation": "https://www.home-assistant.io/components/dlink",
+ "requirements": [
+ "pyW215==0.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py
index de584510008..7164bb2310a 100644
--- a/homeassistant/components/dlink/switch.py
+++ b/homeassistant/components/dlink/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for D-Link W215 smart switch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.dlink/
-"""
+"""Support for D-Link W215 smart switch."""
from datetime import timedelta
import logging
import urllib
@@ -17,8 +12,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as dt_util
-REQUIREMENTS = ['pyW215==0.6.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json
new file mode 100644
index 00000000000..be2e655454e
--- /dev/null
+++ b/homeassistant/components/dlna_dmr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dlna_dmr",
+ "name": "Dlna dmr",
+ "documentation": "https://www.home-assistant.io/components/dlna_dmr",
+ "requirements": [
+ "async-upnp-client==0.14.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py
index 71195d66c69..6f29bd65d56 100644
--- a/homeassistant/components/dlna_dmr/media_player.py
+++ b/homeassistant/components/dlna_dmr/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for DLNA DMR (Device Media Renderer).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.dlna_dmr/
-"""
+"""Support for DLNA DMR (Device Media Renderer)."""
import asyncio
from datetime import datetime
from datetime import timedelta
@@ -32,8 +27,6 @@ from homeassistant.helpers.typing import HomeAssistantType
import homeassistant.helpers.config_validation as cv
from homeassistant.util import get_local_ip
-REQUIREMENTS = ['async-upnp-client==0.14.7']
-
_LOGGER = logging.getLogger(__name__)
DLNA_DMR_DATA = 'dlna_dmr'
diff --git a/homeassistant/components/dnsip/manifest.json b/homeassistant/components/dnsip/manifest.json
new file mode 100644
index 00000000000..2a92f1a0446
--- /dev/null
+++ b/homeassistant/components/dnsip/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dnsip",
+ "name": "Dnsip",
+ "documentation": "https://www.home-assistant.io/components/dnsip",
+ "requirements": [
+ "aiodns==1.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py
index 48cf8debea6..976abb1401b 100644
--- a/homeassistant/components/dnsip/sensor.py
+++ b/homeassistant/components/dnsip/sensor.py
@@ -1,9 +1,4 @@
-"""
-Get your own public IP address or that of any host.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.dnsip/
-"""
+"""Get your own public IP address or that of any host."""
import logging
from datetime import timedelta
@@ -13,8 +8,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['aiodns==1.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_NAME = 'name'
diff --git a/homeassistant/components/dominos/__init__.py b/homeassistant/components/dominos/__init__.py
index 1c8966f3b4b..3c5cb3ed6ec 100644
--- a/homeassistant/components/dominos/__init__.py
+++ b/homeassistant/components/dominos/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import Throttle
-REQUIREMENTS = ['pizzapi==0.0.3']
-
_LOGGER = logging.getLogger(__name__)
# The domain of your component. Should be equal to the name of your component.
@@ -34,8 +32,6 @@ ATTR_ORDER_CODES = 'codes'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
MIN_TIME_BETWEEN_STORE_UPDATES = timedelta(minutes=3330)
-DEPENDENCIES = ['http']
-
_ORDERS_SCHEMA = vol.Schema({
vol.Required(ATTR_ORDER_NAME): cv.string,
vol.Required(ATTR_ORDER_CODES): vol.All(cv.ensure_list, [cv.string]),
diff --git a/homeassistant/components/dominos/manifest.json b/homeassistant/components/dominos/manifest.json
new file mode 100644
index 00000000000..f8d13b49f93
--- /dev/null
+++ b/homeassistant/components/dominos/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "dominos",
+ "name": "Dominos",
+ "documentation": "https://www.home-assistant.io/components/dominos",
+ "requirements": [
+ "pizzapi==0.0.3"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dominos/services.yaml b/homeassistant/components/dominos/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py
index d477836425d..477d96770bc 100644
--- a/homeassistant/components/doorbird/__init__.py
+++ b/homeassistant/components/doorbird/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as dt_util, slugify
-REQUIREMENTS = ['doorbirdpy==2.0.6']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'doorbird'
diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py
index 272a3cb932a..9a20a91c758 100644
--- a/homeassistant/components/doorbird/camera.py
+++ b/homeassistant/components/doorbird/camera.py
@@ -6,13 +6,11 @@ import logging
import aiohttp
import async_timeout
-from homeassistant.components.camera import Camera
+from homeassistant.components.camera import Camera, SUPPORT_STREAM
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import DOMAIN as DOORBIRD_DOMAIN
-DEPENDENCIES = ['doorbird']
-
_CAMERA_LAST_VISITOR = "{} Last Ring"
_CAMERA_LAST_MOTION = "{} Last Motion"
_CAMERA_LIVE = "{} Live"
@@ -32,7 +30,8 @@ async def async_setup_platform(hass, config, async_add_entities,
DoorBirdCamera(
device.live_image_url,
_CAMERA_LIVE.format(doorstation.name),
- _LIVE_INTERVAL),
+ _LIVE_INTERVAL,
+ device.rtsp_live_video_url),
DoorBirdCamera(
device.history_image_url(1, 'doorbell'),
_CAMERA_LAST_VISITOR.format(doorstation.name),
@@ -47,15 +46,27 @@ async def async_setup_platform(hass, config, async_add_entities,
class DoorBirdCamera(Camera):
"""The camera on a DoorBird device."""
- def __init__(self, url, name, interval=None):
+ def __init__(self, url, name, interval=None, stream_url=None):
"""Initialize the camera on a DoorBird device."""
self._url = url
+ self._stream_url = stream_url
self._name = name
self._last_image = None
+ self._supported_features = SUPPORT_STREAM if self._stream_url else 0
self._interval = interval or datetime.timedelta
self._last_update = datetime.datetime.min
super().__init__()
+ @property
+ def stream_source(self):
+ """Return the stream source."""
+ return self._stream_url
+
+ @property
+ def supported_features(self):
+ """Return supported features."""
+ return self._supported_features
+
@property
def name(self):
"""Get the name of the camera."""
diff --git a/homeassistant/components/doorbird/manifest.json b/homeassistant/components/doorbird/manifest.json
new file mode 100644
index 00000000000..3fb9fdc753b
--- /dev/null
+++ b/homeassistant/components/doorbird/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "doorbird",
+ "name": "Doorbird",
+ "documentation": "https://www.home-assistant.io/components/doorbird",
+ "requirements": [
+ "doorbirdpy==2.0.8"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@oblogic7"
+ ]
+}
diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py
index ba6f96660d1..f3b1f5f059e 100644
--- a/homeassistant/components/doorbird/switch.py
+++ b/homeassistant/components/doorbird/switch.py
@@ -6,8 +6,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DOMAIN as DOORBIRD_DOMAIN
-DEPENDENCIES = ['doorbird']
-
_LOGGER = logging.getLogger(__name__)
IR_RELAY = '__ir_light__'
diff --git a/homeassistant/components/dovado/__init__.py b/homeassistant/components/dovado/__init__.py
index df2eed3011a..2a240c2a79e 100644
--- a/homeassistant/components/dovado/__init__.py
+++ b/homeassistant/components/dovado/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['dovado==0.4.1']
-
DOMAIN = 'dovado'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/dovado/manifest.json b/homeassistant/components/dovado/manifest.json
new file mode 100644
index 00000000000..122d774c268
--- /dev/null
+++ b/homeassistant/components/dovado/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dovado",
+ "name": "Dovado",
+ "documentation": "https://www.home-assistant.io/components/dovado",
+ "requirements": [
+ "dovado==0.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dovado/notify.py b/homeassistant/components/dovado/notify.py
index 59827529ed3..f9d9e5574a1 100644
--- a/homeassistant/components/dovado/notify.py
+++ b/homeassistant/components/dovado/notify.py
@@ -8,8 +8,6 @@ from . import DOMAIN as DOVADO_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['dovado']
-
def get_service(hass, config, discovery_info=None):
"""Get the Dovado Router SMS notification service."""
diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py
index 56c4ee03a3a..7a825118fc6 100644
--- a/homeassistant/components/dovado/sensor.py
+++ b/homeassistant/components/dovado/sensor.py
@@ -14,8 +14,6 @@ from . import DOMAIN as DOVADO_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['dovado']
-
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
SENSOR_UPLOAD = 'upload'
diff --git a/homeassistant/components/downloader/manifest.json b/homeassistant/components/downloader/manifest.json
new file mode 100644
index 00000000000..514509c004d
--- /dev/null
+++ b/homeassistant/components/downloader/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "downloader",
+ "name": "Downloader",
+ "documentation": "https://www.home-assistant.io/components/downloader",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/downloader/services.yaml b/homeassistant/components/downloader/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/dsmr/manifest.json b/homeassistant/components/dsmr/manifest.json
new file mode 100644
index 00000000000..21c98d56d1d
--- /dev/null
+++ b/homeassistant/components/dsmr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dsmr",
+ "name": "Dsmr",
+ "documentation": "https://www.home-assistant.io/components/dsmr",
+ "requirements": [
+ "dsmr_parser==0.12"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py
index 6319a68b0c8..d7acc5c28bf 100644
--- a/homeassistant/components/dsmr/sensor.py
+++ b/homeassistant/components/dsmr/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Dutch Smart Meter (also known as Smartmeter or P1 port).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.dsmr/
-"""
+"""Support for Dutch Smart Meter (also known as Smartmeter or P1 port)."""
import asyncio
from datetime import timedelta
from functools import partial
@@ -20,8 +15,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['dsmr_parser==0.12']
-
CONF_DSMR_VERSION = 'dsmr_version'
CONF_RECONNECT_INTERVAL = 'reconnect_interval'
CONF_PRECISION = 'precision'
diff --git a/homeassistant/components/dte_energy_bridge/manifest.json b/homeassistant/components/dte_energy_bridge/manifest.json
new file mode 100644
index 00000000000..fbf7a00f8e6
--- /dev/null
+++ b/homeassistant/components/dte_energy_bridge/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "dte_energy_bridge",
+ "name": "Dte energy bridge",
+ "documentation": "https://www.home-assistant.io/components/dte_energy_bridge",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dte_energy_bridge/sensor.py b/homeassistant/components/dte_energy_bridge/sensor.py
index 5de2fc4a4ee..8610a1e7f70 100644
--- a/homeassistant/components/dte_energy_bridge/sensor.py
+++ b/homeassistant/components/dte_energy_bridge/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring energy usage using the DTE energy bridge.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.dte_energy_bridge/
-"""
+"""Support for monitoring energy usage using the DTE energy bridge."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/dublin_bus_transport/manifest.json b/homeassistant/components/dublin_bus_transport/manifest.json
new file mode 100644
index 00000000000..fc13fddc936
--- /dev/null
+++ b/homeassistant/components/dublin_bus_transport/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "dublin_bus_transport",
+ "name": "Dublin bus transport",
+ "documentation": "https://www.home-assistant.io/components/dublin_bus_transport",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/duckdns/manifest.json b/homeassistant/components/duckdns/manifest.json
new file mode 100644
index 00000000000..ed38d35346f
--- /dev/null
+++ b/homeassistant/components/duckdns/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "duckdns",
+ "name": "Duckdns",
+ "documentation": "https://www.home-assistant.io/components/duckdns",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/duckdns/services.yaml b/homeassistant/components/duckdns/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/duke_energy/manifest.json b/homeassistant/components/duke_energy/manifest.json
new file mode 100644
index 00000000000..602dfec801f
--- /dev/null
+++ b/homeassistant/components/duke_energy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "duke_energy",
+ "name": "Duke energy",
+ "documentation": "https://www.home-assistant.io/components/duke_energy",
+ "requirements": [
+ "pydukeenergy==0.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/duke_energy/sensor.py b/homeassistant/components/duke_energy/sensor.py
index 41d3e5706de..e364e35048b 100644
--- a/homeassistant/components/duke_energy/sensor.py
+++ b/homeassistant/components/duke_energy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Duke Energy Gas and Electric meters.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.duke_energy/
-"""
+"""Support for Duke Energy Gas and Electric meters."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pydukeenergy==0.0.6']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/dunehd/manifest.json b/homeassistant/components/dunehd/manifest.json
new file mode 100644
index 00000000000..35e6c4a2449
--- /dev/null
+++ b/homeassistant/components/dunehd/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dunehd",
+ "name": "Dunehd",
+ "documentation": "https://www.home-assistant.io/components/dunehd",
+ "requirements": [
+ "pdunehd==1.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py
index 796aea86414..a5698c74654 100644
--- a/homeassistant/components/dunehd/media_player.py
+++ b/homeassistant/components/dunehd/media_player.py
@@ -1,9 +1,4 @@
-"""
-DuneHD implementation of the media player.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/media_player.dunehd/
-"""
+"""DuneHD implementation of the media player."""
import voluptuous as vol
from homeassistant.components.media_player import (
@@ -16,8 +11,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pdunehd==1.3']
-
DEFAULT_NAME = 'DuneHD'
CONF_SOURCES = 'sources'
diff --git a/homeassistant/components/dwd_weather_warnings/manifest.json b/homeassistant/components/dwd_weather_warnings/manifest.json
new file mode 100644
index 00000000000..a2b21a9e0bf
--- /dev/null
+++ b/homeassistant/components/dwd_weather_warnings/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "dwd_weather_warnings",
+ "name": "Dwd weather warnings",
+ "documentation": "https://www.home-assistant.io/components/dwd_weather_warnings",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dweet/__init__.py b/homeassistant/components/dweet/__init__.py
index f8e5b181163..148eeeec9a4 100644
--- a/homeassistant/components/dweet/__init__.py
+++ b/homeassistant/components/dweet/__init__.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import state as state_helper
from homeassistant.util import Throttle
-REQUIREMENTS = ['dweepy==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'dweet'
diff --git a/homeassistant/components/dweet/manifest.json b/homeassistant/components/dweet/manifest.json
new file mode 100644
index 00000000000..e0a00620210
--- /dev/null
+++ b/homeassistant/components/dweet/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "dweet",
+ "name": "Dweet",
+ "documentation": "https://www.home-assistant.io/components/dweet",
+ "requirements": [
+ "dweepy==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/dweet/sensor.py b/homeassistant/components/dweet/sensor.py
index d1a64201e6d..55f3c5341a3 100644
--- a/homeassistant/components/dweet/sensor.py
+++ b/homeassistant/components/dweet/sensor.py
@@ -11,8 +11,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_DEVICE)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['dweepy==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Dweet.io Sensor'
diff --git a/homeassistant/components/dyson/__init__.py b/homeassistant/components/dyson/__init__.py
index c2e56436bd8..a857d6657fd 100644
--- a/homeassistant/components/dyson/__init__.py
+++ b/homeassistant/components/dyson/__init__.py
@@ -3,12 +3,10 @@ import logging
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_DEVICES, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME)
from homeassistant.helpers import discovery
-import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['libpurecoollink==0.4.2']
_LOGGER = logging.getLogger(__name__)
@@ -41,7 +39,7 @@ def setup(hass, config):
if DYSON_DEVICES not in hass.data:
hass.data[DYSON_DEVICES] = []
- from libpurecoollink.dyson import DysonAccount
+ from libpurecool.dyson import DysonAccount
dyson_account = DysonAccount(config[DOMAIN].get(CONF_USERNAME),
config[DOMAIN].get(CONF_PASSWORD),
config[DOMAIN].get(CONF_LANGUAGE))
diff --git a/homeassistant/components/dyson/climate.py b/homeassistant/components/dyson/climate.py
index 3e5c976b1f4..a0c4c56d318 100644
--- a/homeassistant/components/dyson/climate.py
+++ b/homeassistant/components/dyson/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for Dyson Pure Hot+Cool link fan.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.dyson/
-"""
+"""Support for Dyson Pure Hot+Cool link fan."""
import logging
from homeassistant.components.climate import ClimateDevice
@@ -11,7 +6,6 @@ from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
-
from . import DYSON_DEVICES
_LOGGER = logging.getLogger(__name__)
@@ -30,7 +24,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None:
return
- from libpurecoollink.dyson_pure_hotcool_link import DysonPureHotCoolLink
+ from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
# Get Dyson Devices from parent component.
add_devices(
[DysonPureHotCoolLinkDevice(device)
@@ -54,7 +48,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
def on_message(self, message):
"""Call when new messages received from the climate."""
- from libpurecoollink.dyson_pure_state import DysonPureHotCoolState
+ from libpurecool.dyson_pure_state import DysonPureHotCoolState
if isinstance(message, DysonPureHotCoolState):
_LOGGER.debug("Message received for climate device %s : %s",
@@ -109,7 +103,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
- from libpurecoollink.const import HeatMode, HeatState
+ from libpurecool.const import HeatMode, HeatState
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
return STATE_HEAT
@@ -124,7 +118,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
@property
def current_fan_mode(self):
"""Return the fan setting."""
- from libpurecoollink.const import FocusMode
+ from libpurecool.const import FocusMode
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
return STATE_FOCUS
return STATE_DIFFUSE
@@ -144,7 +138,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
- from libpurecoollink.const import HeatTarget, HeatMode
+ from libpurecool.const import HeatTarget, HeatMode
self._device.set_configuration(
heat_target=HeatTarget.celsius(target_temp),
heat_mode=HeatMode.HEAT_ON)
@@ -152,7 +146,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
def set_fan_mode(self, fan_mode):
"""Set new fan mode."""
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
- from libpurecoollink.const import FocusMode
+ from libpurecool.const import FocusMode
if fan_mode == STATE_FOCUS:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_ON)
elif fan_mode == STATE_DIFFUSE:
@@ -161,7 +155,7 @@ class DysonPureHotCoolLinkDevice(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
_LOGGER.debug("Set %s heat mode %s", self.name, operation_mode)
- from libpurecoollink.const import HeatMode
+ from libpurecool.const import HeatMode
if operation_mode == STATE_HEAT:
self._device.set_configuration(heat_mode=HeatMode.HEAT_ON)
elif operation_mode == STATE_COOL:
diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py
index 743d301df42..03a55f8abbe 100644
--- a/homeassistant/components/dyson/fan.py
+++ b/homeassistant/components/dyson/fan.py
@@ -7,65 +7,149 @@ import logging
import voluptuous as vol
-from homeassistant.components.fan import (
- DOMAIN, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity)
-from homeassistant.const import CONF_ENTITY_ID
import homeassistant.helpers.config_validation as cv
-
+from homeassistant.components.fan import (
+ SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity,
+ SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH)
+from homeassistant.const import ATTR_ENTITY_ID
from . import DYSON_DEVICES
_LOGGER = logging.getLogger(__name__)
-CONF_NIGHT_MODE = 'night_mode'
+ATTR_NIGHT_MODE = 'night_mode'
+ATTR_AUTO_MODE = 'auto_mode'
+ATTR_ANGLE_LOW = 'angle_low'
+ATTR_ANGLE_HIGH = 'angle_high'
+ATTR_FLOW_DIRECTION_FRONT = 'flow_direction_front'
+ATTR_TIMER = 'timer'
+ATTR_HEPA_FILTER = 'hepa_filter'
+ATTR_CARBON_FILTER = 'carbon_filter'
+ATTR_DYSON_SPEED = 'dyson_speed'
+ATTR_DYSON_SPEED_LIST = 'dyson_speed_list'
-ATTR_IS_NIGHT_MODE = 'is_night_mode'
-ATTR_IS_AUTO_MODE = 'is_auto_mode'
-
-DEPENDENCIES = ['dyson']
+DYSON_DOMAIN = 'dyson'
DYSON_FAN_DEVICES = 'dyson_fan_devices'
-SERVICE_SET_NIGHT_MODE = 'dyson_set_night_mode'
+SERVICE_SET_NIGHT_MODE = 'set_night_mode'
+SERVICE_SET_AUTO_MODE = 'set_auto_mode'
+SERVICE_SET_ANGLE = 'set_angle'
+SERVICE_SET_FLOW_DIRECTION_FRONT = 'set_flow_direction_front'
+SERVICE_SET_TIMER = 'set_timer'
+SERVICE_SET_DYSON_SPEED = 'set_speed'
DYSON_SET_NIGHT_MODE_SCHEMA = vol.Schema({
- vol.Required(CONF_ENTITY_ID): cv.entity_id,
- vol.Required(CONF_NIGHT_MODE): cv.boolean,
+ vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+ vol.Required(ATTR_NIGHT_MODE): cv.boolean,
+})
+
+SET_AUTO_MODE_SCHEMA = vol.Schema({
+ vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+ vol.Required(ATTR_AUTO_MODE): cv.boolean,
+})
+
+SET_ANGLE_SCHEMA = vol.Schema({
+ vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+ vol.Required(ATTR_ANGLE_LOW): cv.positive_int,
+ vol.Required(ATTR_ANGLE_HIGH): cv.positive_int
+})
+
+SET_FLOW_DIRECTION_FRONT_SCHEMA = vol.Schema({
+ vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+ vol.Required(ATTR_FLOW_DIRECTION_FRONT): cv.boolean
+})
+
+SET_TIMER_SCHEMA = vol.Schema({
+ vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+ vol.Required(ATTR_TIMER): cv.positive_int
+})
+
+SET_DYSON_SPEED_SCHEMA = vol.Schema({
+ vol.Required(ATTR_ENTITY_ID): cv.entity_id,
+ vol.Required(ATTR_DYSON_SPEED): cv.positive_int
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Dyson fan components."""
- from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
+ from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
+ from libpurecool.dyson_pure_cool import DysonPureCool
+
+ if discovery_info is None:
+ return
_LOGGER.debug("Creating new Dyson fans")
if DYSON_FAN_DEVICES not in hass.data:
hass.data[DYSON_FAN_DEVICES] = []
# Get Dyson Devices from parent component
- for device in [d for d in hass.data[DYSON_DEVICES] if
- isinstance(d, DysonPureCoolLink)]:
- dyson_entity = DysonPureCoolLinkDevice(hass, device)
- hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
+ has_purecool_devices = False
+ device_serials = [device.serial for device in hass.data[DYSON_FAN_DEVICES]]
+ for device in hass.data[DYSON_DEVICES]:
+ if device.serial not in device_serials:
+ if isinstance(device, DysonPureCool):
+ has_purecool_devices = True
+ dyson_entity = DysonPureCoolDevice(device)
+ hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
+ elif isinstance(device, DysonPureCoolLink):
+ dyson_entity = DysonPureCoolLinkDevice(hass, device)
+ hass.data[DYSON_FAN_DEVICES].append(dyson_entity)
add_entities(hass.data[DYSON_FAN_DEVICES])
def service_handle(service):
"""Handle the Dyson services."""
- entity_id = service.data.get(CONF_ENTITY_ID)
- night_mode = service.data.get(CONF_NIGHT_MODE)
- fan_device = next([fan for fan in hass.data[DYSON_FAN_DEVICES] if
- fan.entity_id == entity_id].__iter__(), None)
+ entity_id = service.data[ATTR_ENTITY_ID]
+ fan_device = next((fan for fan in hass.data[DYSON_FAN_DEVICES] if
+ fan.entity_id == entity_id), None)
if fan_device is None:
_LOGGER.warning("Unable to find Dyson fan device %s",
str(entity_id))
return
if service.service == SERVICE_SET_NIGHT_MODE:
- fan_device.night_mode(night_mode)
+ fan_device.set_night_mode(service.data[ATTR_NIGHT_MODE])
+
+ if service.service == SERVICE_SET_AUTO_MODE:
+ fan_device.set_auto_mode(service.data[ATTR_AUTO_MODE])
+
+ if service.service == SERVICE_SET_ANGLE:
+ fan_device.set_angle(service.data[ATTR_ANGLE_LOW],
+ service.data[ATTR_ANGLE_HIGH])
+
+ if service.service == SERVICE_SET_FLOW_DIRECTION_FRONT:
+ fan_device.set_flow_direction_front(
+ service.data[ATTR_FLOW_DIRECTION_FRONT])
+
+ if service.service == SERVICE_SET_TIMER:
+ fan_device.set_timer(service.data[ATTR_TIMER])
+
+ if service.service == SERVICE_SET_DYSON_SPEED:
+ fan_device.set_dyson_speed(service.data[ATTR_DYSON_SPEED])
# Register dyson service(s)
hass.services.register(
- DOMAIN, SERVICE_SET_NIGHT_MODE, service_handle,
+ DYSON_DOMAIN, SERVICE_SET_NIGHT_MODE, service_handle,
schema=DYSON_SET_NIGHT_MODE_SCHEMA)
+ if has_purecool_devices:
+ hass.services.register(
+ DYSON_DOMAIN, SERVICE_SET_AUTO_MODE, service_handle,
+ schema=SET_AUTO_MODE_SCHEMA)
+
+ hass.services.register(
+ DYSON_DOMAIN, SERVICE_SET_ANGLE, service_handle,
+ schema=SET_ANGLE_SCHEMA)
+
+ hass.services.register(
+ DYSON_DOMAIN, SERVICE_SET_FLOW_DIRECTION_FRONT, service_handle,
+ schema=SET_FLOW_DIRECTION_FRONT_SCHEMA)
+
+ hass.services.register(
+ DYSON_DOMAIN, SERVICE_SET_TIMER, service_handle,
+ schema=SET_TIMER_SCHEMA)
+
+ hass.services.register(
+ DYSON_DOMAIN, SERVICE_SET_DYSON_SPEED, service_handle,
+ schema=SET_DYSON_SPEED_SCHEMA)
class DysonPureCoolLinkDevice(FanEntity):
@@ -84,7 +168,7 @@ class DysonPureCoolLinkDevice(FanEntity):
def on_message(self, message):
"""Call when new messages received from the fan."""
- from libpurecoollink.dyson_pure_state import DysonPureCoolState
+ from libpurecool.dyson_pure_state import DysonPureCoolState
if isinstance(message, DysonPureCoolState):
_LOGGER.debug("Message received for fan device %s: %s", self.name,
@@ -103,7 +187,7 @@ class DysonPureCoolLinkDevice(FanEntity):
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan. Never called ??."""
- from libpurecoollink.const import FanSpeed, FanMode
+ from libpurecool.const import FanSpeed, FanMode
_LOGGER.debug("Set fan speed to: %s", speed)
@@ -116,7 +200,7 @@ class DysonPureCoolLinkDevice(FanEntity):
def turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the fan."""
- from libpurecoollink.const import FanSpeed, FanMode
+ from libpurecool.const import FanSpeed, FanMode
_LOGGER.debug("Turn on fan %s with speed %s", self.name, speed)
if speed:
@@ -132,14 +216,14 @@ class DysonPureCoolLinkDevice(FanEntity):
def turn_off(self, **kwargs) -> None:
"""Turn off the fan."""
- from libpurecoollink.const import FanMode
+ from libpurecool.const import FanMode
_LOGGER.debug("Turn off fan %s", self.name)
self._device.set_configuration(fan_mode=FanMode.OFF)
def oscillate(self, oscillating: bool) -> None:
"""Turn on/off oscillating."""
- from libpurecoollink.const import Oscillation
+ from libpurecool.const import Oscillation
_LOGGER.debug("Turn oscillation %s for device %s", oscillating,
self.name)
@@ -166,7 +250,7 @@ class DysonPureCoolLinkDevice(FanEntity):
@property
def speed(self) -> str:
"""Return the current speed."""
- from libpurecoollink.const import FanSpeed
+ from libpurecool.const import FanSpeed
if self._device.state:
if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
@@ -180,13 +264,13 @@ class DysonPureCoolLinkDevice(FanEntity):
return None
@property
- def is_night_mode(self):
+ def night_mode(self):
"""Return Night mode."""
return self._device.state.night_mode == "ON"
- def night_mode(self, night_mode: bool) -> None:
+ def set_night_mode(self, night_mode: bool) -> None:
"""Turn fan in night mode."""
- from libpurecoollink.const import NightMode
+ from libpurecool.const import NightMode
_LOGGER.debug("Set %s night mode %s", self.name, night_mode)
if night_mode:
@@ -195,13 +279,13 @@ class DysonPureCoolLinkDevice(FanEntity):
self._device.set_configuration(night_mode=NightMode.NIGHT_MODE_OFF)
@property
- def is_auto_mode(self):
+ def auto_mode(self):
"""Return auto mode."""
return self._device.state.fan_mode == "AUTO"
- def auto_mode(self, auto_mode: bool) -> None:
+ def set_auto_mode(self, auto_mode: bool) -> None:
"""Turn fan in auto mode."""
- from libpurecoollink.const import FanMode
+ from libpurecool.const import FanMode
_LOGGER.debug("Set %s auto mode %s", self.name, auto_mode)
if auto_mode:
@@ -212,7 +296,7 @@ class DysonPureCoolLinkDevice(FanEntity):
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
- from libpurecoollink.const import FanSpeed
+ from libpurecool.const import FanSpeed
supported_speeds = [
FanSpeed.FAN_SPEED_AUTO.value,
@@ -239,6 +323,256 @@ class DysonPureCoolLinkDevice(FanEntity):
def device_state_attributes(self) -> dict:
"""Return optional state attributes."""
return {
- ATTR_IS_NIGHT_MODE: self.is_night_mode,
- ATTR_IS_AUTO_MODE: self.is_auto_mode
+ ATTR_NIGHT_MODE: self.night_mode,
+ ATTR_AUTO_MODE: self.auto_mode
}
+
+
+class DysonPureCoolDevice(FanEntity):
+ """Representation of a Dyson Purecool (TP04/DP04) fan."""
+
+ def __init__(self, device):
+ """Initialize the fan."""
+ self._device = device
+
+ async def async_added_to_hass(self):
+ """Call when entity is added to hass."""
+ self.hass.async_add_executor_job(
+ self._device.add_message_listener, self.on_message)
+
+ def on_message(self, message):
+ """Call when new messages received from the fan."""
+ from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State
+
+ if isinstance(message, DysonPureCoolV2State):
+ _LOGGER.debug("Message received for fan device %s: %s", self.name,
+ message)
+ self.schedule_update_ha_state()
+
+ @property
+ def should_poll(self):
+ """No polling needed."""
+ return False
+
+ @property
+ def name(self):
+ """Return the display name of this fan."""
+ return self._device.name
+
+ def turn_on(self, speed: str = None, **kwargs) -> None:
+ """Turn on the fan."""
+ _LOGGER.debug("Turn on fan %s", self.name)
+
+ if speed is not None:
+ self.set_speed(speed)
+ else:
+ self._device.turn_on()
+
+ def set_speed(self, speed: str) -> None:
+ """Set the speed of the fan."""
+ from libpurecool.const import FanSpeed
+ if speed == SPEED_LOW:
+ self._device.set_fan_speed(FanSpeed.FAN_SPEED_4)
+ elif speed == SPEED_MEDIUM:
+ self._device.set_fan_speed(FanSpeed.FAN_SPEED_7)
+ elif speed == SPEED_HIGH:
+ self._device.set_fan_speed(FanSpeed.FAN_SPEED_10)
+
+ def turn_off(self, **kwargs):
+ """Turn off the fan."""
+ _LOGGER.debug("Turn off fan %s", self.name)
+ self._device.turn_off()
+
+ def set_dyson_speed(self, speed: str = None) -> None:
+ """Set the exact speed of the purecool fan."""
+ from libpurecool.const import FanSpeed
+
+ _LOGGER.debug("Set exact speed for fan %s", self.name)
+
+ fan_speed = FanSpeed('{0:04d}'.format(int(speed)))
+ self._device.set_fan_speed(fan_speed)
+
+ def oscillate(self, oscillating: bool) -> None:
+ """Turn on/off oscillating."""
+ _LOGGER.debug("Turn oscillation %s for device %s", oscillating,
+ self.name)
+
+ if oscillating:
+ self._device.enable_oscillation()
+ else:
+ self._device.disable_oscillation()
+
+ def set_night_mode(self, night_mode: bool) -> None:
+ """Turn on/off night mode."""
+ _LOGGER.debug("Turn night mode %s for device %s", night_mode,
+ self.name)
+
+ if night_mode:
+ self._device.enable_night_mode()
+ else:
+ self._device.disable_night_mode()
+
+ def set_auto_mode(self, auto_mode: bool) -> None:
+ """Turn auto mode on/off."""
+ _LOGGER.debug("Turn auto mode %s for device %s", auto_mode,
+ self.name)
+ if auto_mode:
+ self._device.enable_auto_mode()
+ else:
+ self._device.disable_auto_mode()
+
+ def set_angle(self, angle_low: int, angle_high: int) -> None:
+ """Set device angle."""
+ _LOGGER.debug("set low %s and high angle %s for device %s",
+ angle_low, angle_high, self.name)
+ self._device.enable_oscillation(angle_low, angle_high)
+
+ def set_flow_direction_front(self,
+ flow_direction_front: bool) -> None:
+ """Set frontal airflow direction."""
+ _LOGGER.debug("Set frontal flow direction to %s for device %s",
+ flow_direction_front,
+ self.name)
+
+ if flow_direction_front:
+ self._device.enable_frontal_direction()
+ else:
+ self._device.disable_frontal_direction()
+
+ def set_timer(self, timer) -> None:
+ """Set timer."""
+ _LOGGER.debug("Set timer to %s for device %s", timer,
+ self.name)
+
+ if timer == 0:
+ self._device.disable_sleep_timer()
+ else:
+ self._device.enable_sleep_timer(timer)
+
+ @property
+ def oscillating(self):
+ """Return the oscillation state."""
+ return self._device.state and self._device.state.oscillation == "OION"
+
+ @property
+ def is_on(self):
+ """Return true if the entity is on."""
+ if self._device.state:
+ return self._device.state.fan_power == "ON"
+
+ @property
+ def speed(self):
+ """Return the current speed."""
+ from libpurecool.const import FanSpeed
+
+ speed_map = {FanSpeed.FAN_SPEED_1.value: SPEED_LOW,
+ FanSpeed.FAN_SPEED_2.value: SPEED_LOW,
+ FanSpeed.FAN_SPEED_3.value: SPEED_LOW,
+ FanSpeed.FAN_SPEED_4.value: SPEED_LOW,
+ FanSpeed.FAN_SPEED_AUTO.value: SPEED_MEDIUM,
+ FanSpeed.FAN_SPEED_5.value: SPEED_MEDIUM,
+ FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM,
+ FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM,
+ FanSpeed.FAN_SPEED_8.value: SPEED_HIGH,
+ FanSpeed.FAN_SPEED_9.value: SPEED_HIGH}
+
+ return speed_map[self._device.state.speed]
+
+ @property
+ def dyson_speed(self):
+ """Return the current speed."""
+ from libpurecool.const import FanSpeed
+
+ if self._device.state:
+ if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value:
+ return self._device.state.speed
+ return int(self._device.state.speed)
+
+ @property
+ def night_mode(self):
+ """Return Night mode."""
+ return self._device.state.night_mode == "ON"
+
+ @property
+ def auto_mode(self):
+ """Return Auto mode."""
+ return self._device.state.auto_mode == "ON"
+
+ @property
+ def angle_low(self):
+ """Return angle high."""
+ return int(self._device.state.oscillation_angle_low)
+
+ @property
+ def angle_high(self):
+ """Return angle low."""
+ return int(self._device.state.oscillation_angle_high)
+
+ @property
+ def flow_direction_front(self):
+ """Return frontal flow direction."""
+ return self._device.state.front_direction == 'ON'
+
+ @property
+ def timer(self):
+ """Return timer."""
+ return self._device.state.sleep_timer
+
+ @property
+ def hepa_filter(self):
+ """Return the HEPA filter state."""
+ return int(self._device.state.hepa_filter_state)
+
+ @property
+ def carbon_filter(self):
+ """Return the carbon filter state."""
+ return int(self._device.state.carbon_filter_state)
+
+ @property
+ def speed_list(self) -> list:
+ """Get the list of available speeds."""
+ return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
+
+ @property
+ def dyson_speed_list(self) -> list:
+ """Get the list of available dyson speeds."""
+ from libpurecool.const import FanSpeed
+ return [
+ int(FanSpeed.FAN_SPEED_1.value),
+ int(FanSpeed.FAN_SPEED_2.value),
+ int(FanSpeed.FAN_SPEED_3.value),
+ int(FanSpeed.FAN_SPEED_4.value),
+ int(FanSpeed.FAN_SPEED_5.value),
+ int(FanSpeed.FAN_SPEED_6.value),
+ int(FanSpeed.FAN_SPEED_7.value),
+ int(FanSpeed.FAN_SPEED_8.value),
+ int(FanSpeed.FAN_SPEED_9.value),
+ int(FanSpeed.FAN_SPEED_10.value),
+ ]
+
+ @property
+ def device_serial(self):
+ """Return fan's serial number."""
+ return self._device.serial
+
+ @property
+ def supported_features(self) -> int:
+ """Flag supported features."""
+ return SUPPORT_OSCILLATE | \
+ SUPPORT_SET_SPEED
+
+ @property
+ def device_state_attributes(self) -> dict:
+ """Return optional state attributes."""
+ return {
+ ATTR_NIGHT_MODE: self.night_mode,
+ ATTR_AUTO_MODE: self.auto_mode,
+ ATTR_ANGLE_LOW: self.angle_low,
+ ATTR_ANGLE_HIGH: self.angle_high,
+ ATTR_FLOW_DIRECTION_FRONT: self.flow_direction_front,
+ ATTR_TIMER: self.timer,
+ ATTR_HEPA_FILTER: self.hepa_filter,
+ ATTR_CARBON_FILTER: self.carbon_filter,
+ ATTR_DYSON_SPEED: self.dyson_speed,
+ ATTR_DYSON_SPEED_LIST: self.dyson_speed_list
+ }
diff --git a/homeassistant/components/dyson/manifest.json b/homeassistant/components/dyson/manifest.json
new file mode 100644
index 00000000000..7b956dd96c8
--- /dev/null
+++ b/homeassistant/components/dyson/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "dyson",
+ "name": "Dyson",
+ "documentation": "https://www.home-assistant.io/components/dyson",
+ "requirements": [
+ "libpurecool==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py
index ed8987f75c2..56c924d1a54 100644
--- a/homeassistant/components/dyson/sensor.py
+++ b/homeassistant/components/dyson/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Dyson Pure Cool Link Sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.dyson/
-"""
+"""Support for Dyson Pure Cool Link Sensors."""
import logging
from homeassistant.const import STATE_OFF, TEMP_CELSIUS
@@ -11,8 +6,6 @@ from homeassistant.helpers.entity import Entity
from . import DYSON_DEVICES
-DEPENDENCIES = ['dyson']
-
SENSOR_UNITS = {
'air_quality': None,
'dust': None,
@@ -37,9 +30,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
devices = []
unit = hass.config.units.temperature_unit
# Get Dyson Devices from parent component
- from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
- for device in [d for d in hass.data[DYSON_DEVICES] if
- isinstance(d, DysonPureCoolLink)]:
+ from libpurecool.dyson_pure_cool import DysonPureCool
+ from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
+
+ for device in [d for d in hass.data[DYSON_DEVICES]
+ if isinstance(d, DysonPureCoolLink) and
+ not isinstance(d, DysonPureCool)]:
devices.append(DysonFilterLifeSensor(device))
devices.append(DysonDustSensor(device))
devices.append(DysonHumiditySensor(device))
diff --git a/homeassistant/components/dyson/services.yaml b/homeassistant/components/dyson/services.yaml
new file mode 100644
index 00000000000..a93b15b4304
--- /dev/null
+++ b/homeassistant/components/dyson/services.yaml
@@ -0,0 +1,64 @@
+# Describes the format for available fan services
+
+set_night_mode:
+ description: Set the fan in night mode.
+ fields:
+ entity_id:
+ description: Name(s) of the entities to enable/disable night mode
+ example: 'fan.living_room'
+ night_mode:
+ description: Night mode status
+ example: true
+
+set_auto_mode:
+ description: Set the fan in auto mode.
+ fields:
+ entity_id:
+ description: Name(s) of the entities to enable/disable auto mode
+ example: 'fan.living_room'
+ auto_mode:
+ description: Auto mode status
+ example: true
+
+set_angle:
+ description: Set the oscillation angle of the selected fan(s).
+ fields:
+ entity_id:
+ description: Name(s) of the entities for which to set the angle
+ example: 'fan.living_room'
+ angle_low:
+ description: The angle at which the oscillation should start
+ example: 1
+ angle_high:
+ description: The angle at which the oscillation should end
+ example: 255
+
+flow_direction_front:
+ description: Set the fan flow direction.
+ fields:
+ entity_id:
+ description: Name(s) of the entities to set frontal flow direction for
+ example: 'fan.living_room'
+ flow_direction_front:
+ description: Frontal flow direction
+ example: true
+
+set_timer:
+ description: Set the sleep timer.
+ fields:
+ entity_id:
+ description: Name(s) of the entities to set the sleep timer for
+ example: 'fan.living_room'
+ timer:
+ description: The value in minutes to set the timer to, 0 to disable it
+ example: 30
+
+set_speed:
+ description: Set the exact speed of the fan.
+ fields:
+ entity_id:
+ description: Name(s) of the entities to set the speed for
+ example: 'fan.living_room'
+ timer:
+ description: Speed
+ example: 1
\ No newline at end of file
diff --git a/homeassistant/components/dyson/vacuum.py b/homeassistant/components/dyson/vacuum.py
index 7902cfa1585..0bb2368f690 100644
--- a/homeassistant/components/dyson/vacuum.py
+++ b/homeassistant/components/dyson/vacuum.py
@@ -1,9 +1,4 @@
-"""
-Support for the Dyson 360 eye vacuum cleaner robot.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/vacuum.dyson/
-"""
+"""Support for the Dyson 360 eye vacuum cleaner robot."""
import logging
from homeassistant.components.vacuum import (
@@ -20,8 +15,6 @@ ATTR_CLEAN_ID = 'clean_id'
ATTR_FULL_CLEAN_TYPE = 'full_clean_type'
ATTR_POSITION = 'position'
-DEPENDENCIES = ['dyson']
-
DYSON_360_EYE_DEVICES = "dyson_360_eye_devices"
SUPPORT_DYSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \
@@ -31,7 +24,7 @@ SUPPORT_DYSON = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Dyson 360 Eye robot vacuum platform."""
- from libpurecoollink.dyson_360_eye import Dyson360Eye
+ from libpurecool.dyson_360_eye import Dyson360Eye
_LOGGER.debug("Creating new Dyson 360 Eye robot vacuum")
if DYSON_360_EYE_DEVICES not in hass.data:
@@ -81,7 +74,7 @@ class Dyson360EyeDevice(VacuumDevice):
@property
def status(self):
"""Return the status of the vacuum cleaner."""
- from libpurecoollink.const import Dyson360EyeMode
+ from libpurecool.const import Dyson360EyeMode
dyson_labels = {
Dyson360EyeMode.INACTIVE_CHARGING: "Stopped - Charging",
Dyson360EyeMode.INACTIVE_CHARGED: "Stopped - Charged",
@@ -106,7 +99,7 @@ class Dyson360EyeDevice(VacuumDevice):
@property
def fan_speed(self):
"""Return the fan speed of the vacuum cleaner."""
- from libpurecoollink.const import PowerMode
+ from libpurecool.const import PowerMode
speed_labels = {
PowerMode.MAX: "Max",
PowerMode.QUIET: "Quiet"
@@ -128,7 +121,7 @@ class Dyson360EyeDevice(VacuumDevice):
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
- from libpurecoollink.const import Dyson360EyeMode
+ from libpurecool.const import Dyson360EyeMode
return self._device.state.state in [
Dyson360EyeMode.FULL_CLEAN_INITIATED,
@@ -149,7 +142,7 @@ class Dyson360EyeDevice(VacuumDevice):
@property
def battery_icon(self):
"""Return the battery icon for the vacuum cleaner."""
- from libpurecoollink.const import Dyson360EyeMode
+ from libpurecool.const import Dyson360EyeMode
charging = self._device.state.state in [
Dyson360EyeMode.INACTIVE_CHARGING]
@@ -158,7 +151,7 @@ class Dyson360EyeDevice(VacuumDevice):
def turn_on(self, **kwargs):
"""Turn the vacuum on."""
- from libpurecoollink.const import Dyson360EyeMode
+ from libpurecool.const import Dyson360EyeMode
_LOGGER.debug("Turn on device %s", self.name)
if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]:
@@ -178,7 +171,7 @@ class Dyson360EyeDevice(VacuumDevice):
def set_fan_speed(self, fan_speed, **kwargs):
"""Set fan speed."""
- from libpurecoollink.const import PowerMode
+ from libpurecool.const import PowerMode
_LOGGER.debug("Set fan speed %s on device %s", fan_speed, self.name)
power_modes = {
@@ -189,7 +182,7 @@ class Dyson360EyeDevice(VacuumDevice):
def start_pause(self, **kwargs):
"""Start, pause or resume the cleaning task."""
- from libpurecoollink.const import Dyson360EyeMode
+ from libpurecool.const import Dyson360EyeMode
if self._device.state.state in [Dyson360EyeMode.FULL_CLEAN_PAUSED]:
_LOGGER.debug("Resume device %s", self.name)
diff --git a/homeassistant/components/ebox/manifest.json b/homeassistant/components/ebox/manifest.json
new file mode 100644
index 00000000000..16b033df8fd
--- /dev/null
+++ b/homeassistant/components/ebox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ebox",
+ "name": "Ebox",
+ "documentation": "https://www.home-assistant.io/components/ebox",
+ "requirements": [
+ "pyebox==1.1.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ebox/sensor.py b/homeassistant/components/ebox/sensor.py
index 24458e444dc..aaf3384d55f 100644
--- a/homeassistant/components/ebox/sensor.py
+++ b/homeassistant/components/ebox/sensor.py
@@ -21,8 +21,6 @@ from homeassistant.util import Throttle
from homeassistant.exceptions import PlatformNotReady
-REQUIREMENTS = ['pyebox==1.1.4']
-
_LOGGER = logging.getLogger(__name__)
GIGABITS = 'Gb' # type: str
diff --git a/homeassistant/components/ebusd/.translations/th.json b/homeassistant/components/ebusd/.translations/th.json
index 92a8c7969a8..0f12574d8b9 100644
--- a/homeassistant/components/ebusd/.translations/th.json
+++ b/homeassistant/components/ebusd/.translations/th.json
@@ -1,5 +1,6 @@
{
"state": {
+ "day": "\u0e27\u0e31\u0e19",
"night": "\u0e01\u0e25\u0e32\u0e07\u0e04\u0e37\u0e19"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py
index bc1b3aa9595..15ff523f4fb 100644
--- a/homeassistant/components/ebusd/__init__.py
+++ b/homeassistant/components/ebusd/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.util import Throttle
from .const import (DOMAIN, SENSOR_TYPES)
-REQUIREMENTS = ['ebusdpy==0.0.16']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'ebusd'
diff --git a/homeassistant/components/ebusd/manifest.json b/homeassistant/components/ebusd/manifest.json
new file mode 100644
index 00000000000..46b8fb761dc
--- /dev/null
+++ b/homeassistant/components/ebusd/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ebusd",
+ "name": "Ebusd",
+ "documentation": "https://www.home-assistant.io/components/ebusd",
+ "requirements": [
+ "ebusdpy==0.0.16"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py
index 942ba107509..f73bb09b509 100644
--- a/homeassistant/components/ebusd/sensor.py
+++ b/homeassistant/components/ebusd/sensor.py
@@ -6,8 +6,6 @@ from homeassistant.helpers.entity import Entity
from .const import DOMAIN
-DEPENDENCIES = ['ebusd']
-
TIME_FRAME1_BEGIN = 'time_frame1_begin'
TIME_FRAME1_END = 'time_frame1_end'
TIME_FRAME2_BEGIN = 'time_frame2_begin'
diff --git a/homeassistant/components/ecoal_boiler/__init__.py b/homeassistant/components/ecoal_boiler/__init__.py
index 6ab9fc3181c..796324d9337 100644
--- a/homeassistant/components/ecoal_boiler/__init__.py
+++ b/homeassistant/components/ecoal_boiler/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
-REQUIREMENTS = ['ecoaliface==0.4.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = "ecoal_boiler"
diff --git a/homeassistant/components/ecoal_boiler/manifest.json b/homeassistant/components/ecoal_boiler/manifest.json
new file mode 100644
index 00000000000..5bd488e0ff4
--- /dev/null
+++ b/homeassistant/components/ecoal_boiler/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ecoal_boiler",
+ "name": "Ecoal boiler",
+ "documentation": "https://www.home-assistant.io/components/ecoal_boiler",
+ "requirements": [
+ "ecoaliface==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ecoal_boiler/sensor.py b/homeassistant/components/ecoal_boiler/sensor.py
index ef8b39842d9..f1998dd5b2e 100644
--- a/homeassistant/components/ecoal_boiler/sensor.py
+++ b/homeassistant/components/ecoal_boiler/sensor.py
@@ -8,8 +8,6 @@ from . import AVAILABLE_SENSORS, DATA_ECOAL_BOILER
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ecoal_boiler']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ecoal sensors."""
diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py
index db8759a032a..9f286e625a5 100644
--- a/homeassistant/components/ecoal_boiler/switch.py
+++ b/homeassistant/components/ecoal_boiler/switch.py
@@ -8,8 +8,6 @@ from . import AVAILABLE_PUMPS, DATA_ECOAL_BOILER
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ecoal_boiler']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up switches based on ecoal interface."""
diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py
index 167132a5f41..5f9ae6a919d 100644
--- a/homeassistant/components/ecobee/__init__.py
+++ b/homeassistant/components/ecobee/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
from homeassistant.util.json import save_json
-REQUIREMENTS = ['python-ecobee-api==0.0.18']
-
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py
index ca8e551bf5e..0989b9ded97 100644
--- a/homeassistant/components/ecobee/binary_sensor.py
+++ b/homeassistant/components/ecobee/binary_sensor.py
@@ -2,8 +2,6 @@
from homeassistant.components import ecobee
from homeassistant.components.binary_sensor import BinarySensorDevice
-DEPENDENCIES = ['ecobee']
-
ECOBEE_CONFIG_FILE = 'ecobee.conf'
diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py
index 44a3800afa9..3fe1646ee02 100644
--- a/homeassistant/components/ecobee/climate.py
+++ b/homeassistant/components/ecobee/climate.py
@@ -27,8 +27,6 @@ TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
AWAY_MODE = 'awayMode'
-DEPENDENCIES = ['ecobee']
-
SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time'
SERVICE_RESUME_PROGRAM = 'ecobee_resume_program'
diff --git a/homeassistant/components/ecobee/manifest.json b/homeassistant/components/ecobee/manifest.json
new file mode 100644
index 00000000000..d2aa7f0b515
--- /dev/null
+++ b/homeassistant/components/ecobee/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ecobee",
+ "name": "Ecobee",
+ "documentation": "https://www.home-assistant.io/components/ecobee",
+ "requirements": [
+ "python-ecobee-api==0.0.18"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ecobee/notify.py b/homeassistant/components/ecobee/notify.py
index 9824d20b85e..d6e4e8f0c63 100644
--- a/homeassistant/components/ecobee/notify.py
+++ b/homeassistant/components/ecobee/notify.py
@@ -10,8 +10,6 @@ from homeassistant.components.notify import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ecobee']
-
CONF_INDEX = 'index'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py
index 1f9fd5cbde8..436903a645f 100644
--- a/homeassistant/components/ecobee/sensor.py
+++ b/homeassistant/components/ecobee/sensor.py
@@ -4,8 +4,6 @@ from homeassistant.const import (
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity
-DEPENDENCIES = ['ecobee']
-
ECOBEE_CONFIG_FILE = 'ecobee.conf'
SENSOR_TYPES = {
diff --git a/homeassistant/components/ecobee/services.yaml b/homeassistant/components/ecobee/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py
index 2ba5f362b7d..f5058434f38 100644
--- a/homeassistant/components/ecobee/weather.py
+++ b/homeassistant/components/ecobee/weather.py
@@ -7,8 +7,6 @@ from homeassistant.components.weather import (
ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_SPEED, WeatherEntity)
from homeassistant.const import TEMP_FAHRENHEIT
-DEPENDENCIES = ['ecobee']
-
ATTR_FORECAST_TEMP_HIGH = 'temphigh'
ATTR_FORECAST_PRESSURE = 'pressure'
ATTR_FORECAST_VISIBILITY = 'visibility'
diff --git a/homeassistant/components/econet/manifest.json b/homeassistant/components/econet/manifest.json
new file mode 100644
index 00000000000..bd2cd19d519
--- /dev/null
+++ b/homeassistant/components/econet/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "econet",
+ "name": "Econet",
+ "documentation": "https://www.home-assistant.io/components/econet",
+ "requirements": [
+ "pyeconet==0.0.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/econet/services.yaml b/homeassistant/components/econet/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py
index 90176842bf1..4c47e24d705 100644
--- a/homeassistant/components/econet/water_heater.py
+++ b/homeassistant/components/econet/water_heater.py
@@ -13,8 +13,6 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyeconet==0.0.10']
-
_LOGGER = logging.getLogger(__name__)
ATTR_VACATION_START = 'next_vacation_start_date'
diff --git a/homeassistant/components/ecovacs/__init__.py b/homeassistant/components/ecovacs/__init__.py
index 124cae3ca47..da87af722a6 100644
--- a/homeassistant/components/ecovacs/__init__.py
+++ b/homeassistant/components/ecovacs/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['sucks==0.9.3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = "ecovacs"
diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json
new file mode 100644
index 00000000000..d36768fb1b0
--- /dev/null
+++ b/homeassistant/components/ecovacs/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ecovacs",
+ "name": "Ecovacs",
+ "documentation": "https://www.home-assistant.io/components/ecovacs",
+ "requirements": [
+ "sucks==0.9.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@OverloadUT"
+ ]
+}
diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py
index b9fe94f2bed..ee374871d31 100644
--- a/homeassistant/components/ecovacs/vacuum.py
+++ b/homeassistant/components/ecovacs/vacuum.py
@@ -11,8 +11,6 @@ from . import ECOVACS_DEVICES
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ecovacs']
-
SUPPORT_ECOVACS = (
SUPPORT_BATTERY | SUPPORT_RETURN_HOME | SUPPORT_CLEAN_SPOT |
SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | SUPPORT_LOCATE |
diff --git a/homeassistant/components/eddystone_temperature/manifest.json b/homeassistant/components/eddystone_temperature/manifest.json
new file mode 100644
index 00000000000..4684655aa37
--- /dev/null
+++ b/homeassistant/components/eddystone_temperature/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "eddystone_temperature",
+ "name": "Eddystone temperature",
+ "documentation": "https://www.home-assistant.io/components/eddystone_temperature",
+ "requirements": [
+ "beacontools[scan]==1.2.3",
+ "construct==2.9.45"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py
index ae3d498d30c..aad279934e5 100644
--- a/homeassistant/components/eddystone_temperature/sensor.py
+++ b/homeassistant/components/eddystone_temperature/sensor.py
@@ -18,8 +18,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['beacontools[scan]==1.2.3', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
CONF_BEACONS = 'beacons'
diff --git a/homeassistant/components/edimax/manifest.json b/homeassistant/components/edimax/manifest.json
new file mode 100644
index 00000000000..9fe0e4c50c9
--- /dev/null
+++ b/homeassistant/components/edimax/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "edimax",
+ "name": "Edimax",
+ "documentation": "https://www.home-assistant.io/components/edimax",
+ "requirements": [
+ "pyedimax==0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/edimax/switch.py b/homeassistant/components/edimax/switch.py
index 90ad3fff57f..535ae65800f 100644
--- a/homeassistant/components/edimax/switch.py
+++ b/homeassistant/components/edimax/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Edimax switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.edimax/
-"""
+"""Support for Edimax switches."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyedimax==0.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Edimax Smart Plug'
diff --git a/homeassistant/components/edp_redy/__init__.py b/homeassistant/components/edp_redy/__init__.py
index 9b8bfaa437a..af012064194 100644
--- a/homeassistant/components/edp_redy/__init__.py
+++ b/homeassistant/components/edp_redy/__init__.py
@@ -20,8 +20,6 @@ EDP_REDY = 'edp_redy'
DATA_UPDATE_TOPIC = '{0}_data_update'.format(DOMAIN)
UPDATE_INTERVAL = 60
-REQUIREMENTS = ['edp_redy==0.0.3']
-
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
diff --git a/homeassistant/components/edp_redy/manifest.json b/homeassistant/components/edp_redy/manifest.json
new file mode 100644
index 00000000000..90404b21678
--- /dev/null
+++ b/homeassistant/components/edp_redy/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "edp_redy",
+ "name": "Edp redy",
+ "documentation": "https://www.home-assistant.io/components/edp_redy",
+ "requirements": [
+ "edp_redy==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@abmantis"
+ ]
+}
diff --git a/homeassistant/components/edp_redy/sensor.py b/homeassistant/components/edp_redy/sensor.py
index b8f9c031c29..cf9766ede66 100644
--- a/homeassistant/components/edp_redy/sensor.py
+++ b/homeassistant/components/edp_redy/sensor.py
@@ -8,8 +8,6 @@ from . import EDP_REDY, EdpRedyDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['edp_redy']
-
# Load power in watts (W)
ATTR_ACTIVE_POWER = 'active_power'
diff --git a/homeassistant/components/edp_redy/switch.py b/homeassistant/components/edp_redy/switch.py
index 0c92f80ccf6..3f6dfe6b82d 100644
--- a/homeassistant/components/edp_redy/switch.py
+++ b/homeassistant/components/edp_redy/switch.py
@@ -7,8 +7,6 @@ from . import EDP_REDY, EdpRedyDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['edp_redy']
-
# Load power in watts (W)
ATTR_ACTIVE_POWER = 'active_power'
diff --git a/homeassistant/components/ee_brightbox/device_tracker.py b/homeassistant/components/ee_brightbox/device_tracker.py
index fc23abda1db..6af5065ed2e 100644
--- a/homeassistant/components/ee_brightbox/device_tracker.py
+++ b/homeassistant/components/ee_brightbox/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for EE Brightbox router.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.ee_brightbox/
-"""
+"""Support for EE Brightbox router."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['eebrightbox==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_VERSION = 'version'
diff --git a/homeassistant/components/ee_brightbox/manifest.json b/homeassistant/components/ee_brightbox/manifest.json
new file mode 100644
index 00000000000..967f04228a8
--- /dev/null
+++ b/homeassistant/components/ee_brightbox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ee_brightbox",
+ "name": "Ee brightbox",
+ "documentation": "https://www.home-assistant.io/components/ee_brightbox",
+ "requirements": [
+ "eebrightbox==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/efergy/manifest.json b/homeassistant/components/efergy/manifest.json
new file mode 100644
index 00000000000..f4ca116a647
--- /dev/null
+++ b/homeassistant/components/efergy/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "efergy",
+ "name": "Efergy",
+ "documentation": "https://www.home-assistant.io/components/efergy",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py
index b3c40b4fa25..eb8912abe18 100644
--- a/homeassistant/components/efergy/sensor.py
+++ b/homeassistant/components/efergy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Efergy sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.efergy/
-"""
+"""Support for Efergy sensors."""
import logging
import requests
diff --git a/homeassistant/components/egardia/__init__.py b/homeassistant/components/egardia/__init__.py
index fe613824c95..cf0bb20f0fc 100644
--- a/homeassistant/components/egardia/__init__.py
+++ b/homeassistant/components/egardia/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pythonegardia==1.0.39']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DISCOVER_DEVICES = 'egardia_sensor'
diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py
index 7fc60d5fb5d..ab48181f9ed 100644
--- a/homeassistant/components/egardia/alarm_control_panel.py
+++ b/homeassistant/components/egardia/alarm_control_panel.py
@@ -13,8 +13,6 @@ from . import (
CONF_REPORT_SERVER_PORT, EGARDIA_DEVICE, EGARDIA_SERVER,
REPORT_SERVER_CODES_IGNORE)
-DEPENDENCIES = ['egardia']
-
_LOGGER = logging.getLogger(__name__)
STATES = {
diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py
index d11894ae675..965b2dd1d55 100644
--- a/homeassistant/components/egardia/binary_sensor.py
+++ b/homeassistant/components/egardia/binary_sensor.py
@@ -8,8 +8,6 @@ from . import ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['egardia']
-
EGARDIA_TYPE_TO_DEVICE_CLASS = {
'IR Sensor': 'motion',
'Door Contact': 'opening',
diff --git a/homeassistant/components/egardia/manifest.json b/homeassistant/components/egardia/manifest.json
new file mode 100644
index 00000000000..3a95b90db99
--- /dev/null
+++ b/homeassistant/components/egardia/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "egardia",
+ "name": "Egardia",
+ "documentation": "https://www.home-assistant.io/components/egardia",
+ "requirements": [
+ "pythonegardia==1.0.39"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@jeroenterheerdt"
+ ]
+}
diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py
index ca6c8a5a5c6..d74218796a3 100644
--- a/homeassistant/components/eight_sleep/__init__.py
+++ b/homeassistant/components/eight_sleep/__init__.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['pyeight==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_PARTNER = 'partner'
diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py
index a3ca27b570d..b3842106723 100644
--- a/homeassistant/components/eight_sleep/binary_sensor.py
+++ b/homeassistant/components/eight_sleep/binary_sensor.py
@@ -7,8 +7,6 @@ from . import CONF_BINARY_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['eight_sleep']
-
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/eight_sleep/manifest.json b/homeassistant/components/eight_sleep/manifest.json
new file mode 100644
index 00000000000..2b008c3c370
--- /dev/null
+++ b/homeassistant/components/eight_sleep/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "eight_sleep",
+ "name": "Eight sleep",
+ "documentation": "https://www.home-assistant.io/components/eight_sleep",
+ "requirements": [
+ "pyeight==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@mezz64"
+ ]
+}
diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py
index a1ad93ec54a..b7b0f588155 100644
--- a/homeassistant/components/eight_sleep/sensor.py
+++ b/homeassistant/components/eight_sleep/sensor.py
@@ -5,8 +5,6 @@ from . import (
CONF_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity,
EightSleepUserEntity)
-DEPENDENCIES = ['eight_sleep']
-
ATTR_ROOM_TEMP = 'Room Temperature'
ATTR_AVG_ROOM_TEMP = 'Average Room Temperature'
ATTR_BED_TEMP = 'Bed Temperature'
diff --git a/homeassistant/components/eliqonline/manifest.json b/homeassistant/components/eliqonline/manifest.json
new file mode 100644
index 00000000000..98d94fd009e
--- /dev/null
+++ b/homeassistant/components/eliqonline/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "eliqonline",
+ "name": "Eliqonline",
+ "documentation": "https://www.home-assistant.io/components/eliqonline",
+ "requirements": [
+ "eliqonline==1.2.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/eliqonline/sensor.py b/homeassistant/components/eliqonline/sensor.py
index b03164a30d4..03d6ad89591 100644
--- a/homeassistant/components/eliqonline/sensor.py
+++ b/homeassistant/components/eliqonline/sensor.py
@@ -1,11 +1,7 @@
-"""
-Monitors home energy use for the ELIQ Online service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.eliqonline/
-"""
+"""Monitors home energy use for the ELIQ Online service."""
from datetime import timedelta
import logging
+import asyncio
import voluptuous as vol
@@ -15,8 +11,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['eliqonline==1.2.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_CHANNEL_ID = 'channel_id'
@@ -97,6 +91,6 @@ class EliqSensor(Entity):
_LOGGER.debug("Updated power from server %d W", self._state)
except KeyError:
_LOGGER.warning("Invalid response from ELIQ Online API")
- except OSError as error:
+ except (OSError, asyncio.TimeoutError) as error:
_LOGGER.warning("Could not connect to the ELIQ Online API: %s",
error)
diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py
index a0c08bf5429..564f0e74c75 100644
--- a/homeassistant/components/elkm1/__init__.py
+++ b/homeassistant/components/elkm1/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType # noqa
-REQUIREMENTS = ['elkm1-lib==0.7.13']
-
DOMAIN = 'elkm1'
CONF_AREA = 'area'
diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py
index e9155dd17b5..b885913a0df 100644
--- a/homeassistant/components/elkm1/alarm_control_panel.py
+++ b/homeassistant/components/elkm1/alarm_control_panel.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.dispatcher import (
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
-DEPENDENCIES = [ELK_DOMAIN]
-
SIGNAL_ARM_ENTITY = 'elkm1_arm'
SIGNAL_DISPLAY_MESSAGE = 'elkm1_display_message'
diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py
index 93e4aa66b23..23c18312863 100644
--- a/homeassistant/components/elkm1/climate.py
+++ b/homeassistant/components/elkm1/climate.py
@@ -9,8 +9,6 @@ from homeassistant.const import PRECISION_WHOLE, STATE_ON
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
-DEPENDENCIES = [ELK_DOMAIN]
-
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py
index fe84ab3f251..ee6fe09a7a2 100644
--- a/homeassistant/components/elkm1/light.py
+++ b/homeassistant/components/elkm1/light.py
@@ -4,8 +4,6 @@ from homeassistant.components.light import (
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
-DEPENDENCIES = [ELK_DOMAIN]
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json
new file mode 100644
index 00000000000..73b48623260
--- /dev/null
+++ b/homeassistant/components/elkm1/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "elkm1",
+ "name": "Elkm1",
+ "documentation": "https://www.home-assistant.io/components/elkm1",
+ "requirements": [
+ "elkm1-lib==0.7.13"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py
index 1d08f4cf96d..aaae8bb0a5c 100644
--- a/homeassistant/components/elkm1/scene.py
+++ b/homeassistant/components/elkm1/scene.py
@@ -3,8 +3,6 @@ from homeassistant.components.scene import Scene
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
-DEPENDENCIES = [ELK_DOMAIN]
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py
index da27a3ac4b1..0e367265605 100644
--- a/homeassistant/components/elkm1/sensor.py
+++ b/homeassistant/components/elkm1/sensor.py
@@ -1,8 +1,6 @@
"""Support for control of ElkM1 sensors."""
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
-DEPENDENCIES = [ELK_DOMAIN]
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py
index 740a2965865..df29491435e 100644
--- a/homeassistant/components/elkm1/switch.py
+++ b/homeassistant/components/elkm1/switch.py
@@ -3,8 +3,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities
-DEPENDENCIES = [ELK_DOMAIN]
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/emby/manifest.json b/homeassistant/components/emby/manifest.json
new file mode 100644
index 00000000000..87688733e59
--- /dev/null
+++ b/homeassistant/components/emby/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "emby",
+ "name": "Emby",
+ "documentation": "https://www.home-assistant.io/components/emby",
+ "requirements": [
+ "pyemby==1.6"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@mezz64"
+ ]
+}
diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py
index b1259db913d..fa1c096707b 100644
--- a/homeassistant/components/emby/media_player.py
+++ b/homeassistant/components/emby/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support to interface with the Emby API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.emby/
-"""
+"""Support to interface with the Emby API."""
import logging
import voluptuous as vol
@@ -22,8 +17,6 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pyemby==1.6']
-
_LOGGER = logging.getLogger(__name__)
CONF_AUTO_HIDE = 'auto_hide'
diff --git a/homeassistant/components/emoncms/manifest.json b/homeassistant/components/emoncms/manifest.json
new file mode 100644
index 00000000000..90623c01d1b
--- /dev/null
+++ b/homeassistant/components/emoncms/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "emoncms",
+ "name": "Emoncms",
+ "documentation": "https://www.home-assistant.io/components/emoncms",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py
index 5d619878d98..6e059e1a30f 100644
--- a/homeassistant/components/emoncms/sensor.py
+++ b/homeassistant/components/emoncms/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring emoncms feeds.
-
-For more details about this component, please refer to the documentation
-at https://home-assistant.io/components/sensor.emoncms/
-"""
+"""Support for monitoring emoncms feeds."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/emoncms_history/manifest.json b/homeassistant/components/emoncms_history/manifest.json
new file mode 100644
index 00000000000..0cb09e3fb73
--- /dev/null
+++ b/homeassistant/components/emoncms_history/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "emoncms_history",
+ "name": "Emoncms history",
+ "documentation": "https://www.home-assistant.io/components/emoncms_history",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py
index c8ed263a2dc..2ef0aaca134 100644
--- a/homeassistant/components/emulated_hue/__init__.py
+++ b/homeassistant/components/emulated_hue/__init__.py
@@ -8,7 +8,6 @@ from homeassistant import util
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
)
-from homeassistant.components.http import REQUIREMENTS # NOQA
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.deprecation import get_deprecated
import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py
index 4c329cac28f..44a9c6e53ef 100644
--- a/homeassistant/components/emulated_hue/hue_api.py
+++ b/homeassistant/components/emulated_hue/hue_api.py
@@ -4,42 +4,42 @@ import logging
from aiohttp import web
from homeassistant import core
-from homeassistant.const import (
- ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
- SERVICE_VOLUME_SET, SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, STATE_ON,
- STATE_OFF, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, ATTR_SUPPORTED_FEATURES
-)
-from homeassistant.components.light import (
- ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS
-)
+from homeassistant.components import (
+ climate, cover, fan, light, media_player, scene, script)
from homeassistant.components.climate.const import (
- SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE
-)
-from homeassistant.components.media_player.const import (
- ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET,
-)
-from homeassistant.components.fan import (
- ATTR_SPEED, SUPPORT_SET_SPEED, SPEED_OFF, SPEED_LOW,
- SPEED_MEDIUM, SPEED_HIGH
-)
-
+ SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION, ATTR_POSITION, SERVICE_SET_COVER_POSITION,
- SUPPORT_SET_POSITION
-)
-
-from homeassistant.components import (
- climate, cover, fan, media_player, light, script, scene
-)
-
+ SUPPORT_SET_POSITION)
+from homeassistant.components.fan import (
+ ATTR_SPEED, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF,
+ SUPPORT_SET_SPEED)
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.const import KEY_REAL_IP
+from homeassistant.components.light import (
+ ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR)
+from homeassistant.components.media_player.const import (
+ ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
+ HTTP_BAD_REQUEST, HTTP_NOT_FOUND, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER,
+ SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET, STATE_OFF, STATE_ON)
from homeassistant.util.network import is_local
_LOGGER = logging.getLogger(__name__)
HUE_API_STATE_ON = 'on'
HUE_API_STATE_BRI = 'bri'
+HUE_API_STATE_HUE = 'hue'
+HUE_API_STATE_SAT = 'sat'
+
+HUE_API_STATE_HUE_MAX = 65535.0
+HUE_API_STATE_SAT_MAX = 254.0
+HUE_API_STATE_BRI_MAX = 255.0
+
+STATE_BRIGHTNESS = HUE_API_STATE_BRI
+STATE_HUE = HUE_API_STATE_HUE
+STATE_SATURATION = HUE_API_STATE_SAT
class HueUsernameView(HomeAssistantView):
@@ -140,11 +140,11 @@ class HueAllLightsStateView(HomeAssistantView):
for entity in hass.states.async_all():
if self.config.is_entity_exposed(entity):
- state, brightness = get_entity_state(self.config, entity)
+ state = get_entity_state(self.config, entity)
number = self.config.entity_id_to_number(entity.entity_id)
- json_response[number] = entity_to_json(
- self.config, entity, state, brightness)
+ json_response[number] = entity_to_json(self.config,
+ entity, state)
return self.json(json_response)
@@ -179,9 +179,9 @@ class HueOneLightStateView(HomeAssistantView):
_LOGGER.error('Entity not exposed: %s', entity_id)
return web.Response(text="Entity not exposed", status=404)
- state, brightness = get_entity_state(self.config, entity)
+ state = get_entity_state(self.config, entity)
- json_response = entity_to_json(self.config, entity, state, brightness)
+ json_response = entity_to_json(self.config, entity, state)
return self.json(json_response)
@@ -234,8 +234,6 @@ class HueOneLightChangeView(HomeAssistantView):
_LOGGER.error('Unable to parse data: %s', request_json)
return web.Response(text="Bad request", status=400)
- result, brightness = parsed
-
# Choose general HA domain
domain = core.DOMAIN
@@ -243,7 +241,7 @@ class HueOneLightChangeView(HomeAssistantView):
turn_on_needed = False
# Convert the resulting "on" status into the service we need to call
- service = SERVICE_TURN_ON if result else SERVICE_TURN_OFF
+ service = SERVICE_TURN_ON if parsed[STATE_ON] else SERVICE_TURN_OFF
# Construct what we need to send to the service
data = {ATTR_ENTITY_ID: entity_id}
@@ -252,18 +250,32 @@ class HueOneLightChangeView(HomeAssistantView):
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if entity.domain == light.DOMAIN:
- if entity_features & SUPPORT_BRIGHTNESS:
- if brightness is not None:
- data[ATTR_BRIGHTNESS] = brightness
+ if parsed[STATE_ON]:
+ if entity_features & SUPPORT_BRIGHTNESS:
+ if parsed[STATE_BRIGHTNESS] is not None:
+ data[ATTR_BRIGHTNESS] = parsed[STATE_BRIGHTNESS]
+ if entity_features & SUPPORT_COLOR:
+ if parsed[STATE_HUE] is not None:
+ if parsed[STATE_SATURATION]:
+ sat = parsed[STATE_SATURATION]
+ else:
+ sat = 0
+ hue = parsed[STATE_HUE]
+
+ # Convert hs values to hass hs values
+ sat = int((sat / HUE_API_STATE_SAT_MAX) * 100)
+ hue = int((hue / HUE_API_STATE_HUE_MAX) * 360)
+
+ data[ATTR_HS_COLOR] = (hue, sat)
# If the requested entity is a script add some variables
elif entity.domain == script.DOMAIN:
data['variables'] = {
- 'requested_state': STATE_ON if result else STATE_OFF
+ 'requested_state': STATE_ON if parsed[STATE_ON] else STATE_OFF
}
- if brightness is not None:
- data['variables']['requested_level'] = brightness
+ if parsed[STATE_BRIGHTNESS] is not None:
+ data['variables']['requested_level'] = parsed[STATE_BRIGHTNESS]
# If the requested entity is a climate, set the temperature
elif entity.domain == climate.DOMAIN:
@@ -272,20 +284,21 @@ class HueOneLightChangeView(HomeAssistantView):
service = None
if entity_features & SUPPORT_TARGET_TEMPERATURE:
- if brightness is not None:
+ if parsed[STATE_BRIGHTNESS] is not None:
domain = entity.domain
service = SERVICE_SET_TEMPERATURE
- data[ATTR_TEMPERATURE] = brightness
+ data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS]
# If the requested entity is a media player, convert to volume
elif entity.domain == media_player.DOMAIN:
if entity_features & SUPPORT_VOLUME_SET:
- if brightness is not None:
+ if parsed[STATE_BRIGHTNESS] is not None:
turn_on_needed = True
domain = entity.domain
service = SERVICE_VOLUME_SET
# Convert 0-100 to 0.0-1.0
- data[ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100.0
+ data[ATTR_MEDIA_VOLUME_LEVEL] = \
+ parsed[STATE_BRIGHTNESS] / 100.0
# If the requested entity is a cover, convert to open_cover/close_cover
elif entity.domain == cover.DOMAIN:
@@ -296,17 +309,18 @@ class HueOneLightChangeView(HomeAssistantView):
service = SERVICE_CLOSE_COVER
if entity_features & SUPPORT_SET_POSITION:
- if brightness is not None:
+ if parsed[STATE_BRIGHTNESS] is not None:
domain = entity.domain
service = SERVICE_SET_COVER_POSITION
- data[ATTR_POSITION] = brightness
+ data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS]
# If the requested entity is a fan, convert to speed
elif entity.domain == fan.DOMAIN:
if entity_features & SUPPORT_SET_SPEED:
- if brightness is not None:
+ if parsed[STATE_BRIGHTNESS] is not None:
domain = entity.domain
# Convert 0-100 to a fan speed
+ brightness = parsed[STATE_BRIGHTNESS]
if brightness == 0:
data[ATTR_SPEED] = SPEED_OFF
elif 0 < brightness <= 33.3:
@@ -325,7 +339,7 @@ class HueOneLightChangeView(HomeAssistantView):
# they'll map to "on". Thus, instead of reporting its actual
# status, we report what Alexa will want to see, which is the same
# as the actual requested command.
- config.cached_states[entity_id] = (result, brightness)
+ config.cached_states[entity_id] = parsed
# Separate call to turn on needed
if turn_on_needed:
@@ -338,73 +352,120 @@ class HueOneLightChangeView(HomeAssistantView):
domain, service, data, blocking=True))
json_response = \
- [create_hue_success_response(entity_id, HUE_API_STATE_ON, result)]
+ [create_hue_success_response(
+ entity_id, HUE_API_STATE_ON, parsed[STATE_ON])]
- if brightness is not None:
+ if parsed[STATE_BRIGHTNESS] is not None:
json_response.append(create_hue_success_response(
- entity_id, HUE_API_STATE_BRI, brightness))
+ entity_id, HUE_API_STATE_BRI, parsed[STATE_BRIGHTNESS]))
+ if parsed[STATE_HUE] is not None:
+ json_response.append(create_hue_success_response(
+ entity_id, HUE_API_STATE_HUE, parsed[STATE_HUE]))
+ if parsed[STATE_SATURATION] is not None:
+ json_response.append(create_hue_success_response(
+ entity_id, HUE_API_STATE_SAT, parsed[STATE_SATURATION]))
return self.json(json_response)
def parse_hue_api_put_light_body(request_json, entity):
"""Parse the body of a request to change the state of a light."""
+ data = {
+ STATE_BRIGHTNESS: None,
+ STATE_HUE: None,
+ STATE_ON: False,
+ STATE_SATURATION: None,
+ }
+
+ # Make sure the entity actually supports brightness
+ entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
+
if HUE_API_STATE_ON in request_json:
if not isinstance(request_json[HUE_API_STATE_ON], bool):
return None
- if request_json['on']:
+ if request_json[HUE_API_STATE_ON]:
# Echo requested device be turned on
- brightness = None
- report_brightness = False
- result = True
+ data[STATE_BRIGHTNESS] = None
+ data[STATE_ON] = True
else:
# Echo requested device be turned off
- brightness = None
- report_brightness = False
- result = False
+ data[STATE_BRIGHTNESS] = None
+ data[STATE_ON] = False
+
+ if HUE_API_STATE_HUE in request_json:
+ try:
+ # Clamp brightness from 0 to 65535
+ data[STATE_HUE] = \
+ max(0, min(int(request_json[HUE_API_STATE_HUE]),
+ HUE_API_STATE_HUE_MAX))
+ except ValueError:
+ return None
+
+ if HUE_API_STATE_SAT in request_json:
+ try:
+ # Clamp saturation from 0 to 254
+ data[STATE_SATURATION] = \
+ max(0, min(int(request_json[HUE_API_STATE_SAT]),
+ HUE_API_STATE_SAT_MAX))
+ except ValueError:
+ return None
if HUE_API_STATE_BRI in request_json:
try:
# Clamp brightness from 0 to 255
- brightness = \
- max(0, min(int(request_json[HUE_API_STATE_BRI]), 255))
+ data[STATE_BRIGHTNESS] = \
+ max(0, min(int(request_json[HUE_API_STATE_BRI]),
+ HUE_API_STATE_BRI_MAX))
except ValueError:
return None
- # Make sure the entity actually supports brightness
- entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
-
if entity.domain == light.DOMAIN:
- if entity_features & SUPPORT_BRIGHTNESS:
- report_brightness = True
- result = (brightness > 0)
+ data[STATE_ON] = (data[STATE_BRIGHTNESS] > 0)
+ if not entity_features & SUPPORT_BRIGHTNESS:
+ data[STATE_BRIGHTNESS] = None
elif entity.domain == scene.DOMAIN:
- brightness = None
- report_brightness = False
- result = True
+ data[STATE_BRIGHTNESS] = None
+ data[STATE_ON] = True
elif entity.domain in [
script.DOMAIN, media_player.DOMAIN,
fan.DOMAIN, cover.DOMAIN, climate.DOMAIN]:
# Convert 0-255 to 0-100
- level = brightness / 255 * 100
- brightness = round(level)
- report_brightness = True
- result = True
+ level = (data[STATE_BRIGHTNESS] / HUE_API_STATE_BRI_MAX) * 100
+ data[STATE_BRIGHTNESS] = round(level)
+ data[STATE_ON] = True
- return (result, brightness) if report_brightness else (result, None)
+ return data
def get_entity_state(config, entity):
"""Retrieve and convert state and brightness values for an entity."""
cached_state = config.cached_states.get(entity.entity_id, None)
+ data = {
+ STATE_BRIGHTNESS: None,
+ STATE_HUE: None,
+ STATE_ON: False,
+ STATE_SATURATION: None
+ }
if cached_state is None:
- final_state = entity.state != STATE_OFF
- final_brightness = entity.attributes.get(
- ATTR_BRIGHTNESS, 255 if final_state else 0)
+ data[STATE_ON] = entity.state != STATE_OFF
+ if data[STATE_ON]:
+ data[STATE_BRIGHTNESS] = entity.attributes.get(ATTR_BRIGHTNESS)
+ hue_sat = entity.attributes.get(ATTR_HS_COLOR, None)
+ if hue_sat is not None:
+ hue = hue_sat[0]
+ sat = hue_sat[1]
+ # convert hass hs values back to hue hs values
+ data[STATE_HUE] = int((hue / 360.0) * HUE_API_STATE_HUE_MAX)
+ data[STATE_SATURATION] = \
+ int((sat / 100.0) * HUE_API_STATE_SAT_MAX)
+ else:
+ data[STATE_BRIGHTNESS] = 0
+ data[STATE_HUE] = 0
+ data[STATE_SATURATION] = 0
# Make sure the entity actually supports brightness
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
@@ -416,41 +477,53 @@ def get_entity_state(config, entity):
elif entity.domain == climate.DOMAIN:
temperature = entity.attributes.get(ATTR_TEMPERATURE, 0)
# Convert 0-100 to 0-255
- final_brightness = round(temperature * 255 / 100)
+ data[STATE_BRIGHTNESS] = round(temperature * 255 / 100)
elif entity.domain == media_player.DOMAIN:
level = entity.attributes.get(
- ATTR_MEDIA_VOLUME_LEVEL, 1.0 if final_state else 0.0)
+ ATTR_MEDIA_VOLUME_LEVEL, 1.0 if data[STATE_ON] else 0.0)
# Convert 0.0-1.0 to 0-255
- final_brightness = round(min(1.0, level) * 255)
+ data[STATE_BRIGHTNESS] = \
+ round(min(1.0, level) * HUE_API_STATE_BRI_MAX)
elif entity.domain == fan.DOMAIN:
speed = entity.attributes.get(ATTR_SPEED, 0)
# Convert 0.0-1.0 to 0-255
- final_brightness = 0
+ data[STATE_BRIGHTNESS] = 0
if speed == SPEED_LOW:
- final_brightness = 85
+ data[STATE_BRIGHTNESS] = 85
elif speed == SPEED_MEDIUM:
- final_brightness = 170
+ data[STATE_BRIGHTNESS] = 170
elif speed == SPEED_HIGH:
- final_brightness = 255
+ data[STATE_BRIGHTNESS] = 255
elif entity.domain == cover.DOMAIN:
level = entity.attributes.get(ATTR_CURRENT_POSITION, 0)
- final_brightness = round(level / 100 * 255)
+ data[STATE_BRIGHTNESS] = round(level / 100 * HUE_API_STATE_BRI_MAX)
else:
- final_state, final_brightness = cached_state
+ data = cached_state
# Make sure brightness is valid
- if final_brightness is None:
- final_brightness = 255 if final_state else 0
+ if data[STATE_BRIGHTNESS] is None:
+ data[STATE_BRIGHTNESS] = 255 if data[STATE_ON] else 0
+ # Make sure hue/saturation are valid
+ if (data[STATE_HUE] is None) or (data[STATE_SATURATION] is None):
+ data[STATE_HUE] = 0
+ data[STATE_SATURATION] = 0
- return (final_state, final_brightness)
+ # If the light is off, set the color to off
+ if data[STATE_BRIGHTNESS] == 0:
+ data[STATE_HUE] = 0
+ data[STATE_SATURATION] = 0
+
+ return data
-def entity_to_json(config, entity, is_on=None, brightness=None):
+def entity_to_json(config, entity, state):
"""Convert an entity to its Hue bridge JSON representation."""
return {
'state':
{
- HUE_API_STATE_ON: is_on,
- HUE_API_STATE_BRI: brightness,
+ HUE_API_STATE_ON: state[STATE_ON],
+ HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
+ HUE_API_STATE_HUE: state[STATE_HUE],
+ HUE_API_STATE_SAT: state[STATE_SATURATION],
'reachable': True
},
'type': 'Dimmable light',
diff --git a/homeassistant/components/emulated_hue/manifest.json b/homeassistant/components/emulated_hue/manifest.json
new file mode 100644
index 00000000000..75fcbc4c555
--- /dev/null
+++ b/homeassistant/components/emulated_hue/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "emulated_hue",
+ "name": "Emulated hue",
+ "documentation": "https://www.home-assistant.io/components/emulated_hue",
+ "requirements": [
+ "aiohttp_cors==0.7.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/emulated_hue/services.yaml b/homeassistant/components/emulated_hue/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/emulated_roku/.translations/es.json b/homeassistant/components/emulated_roku/.translations/es.json
index 3491c784c19..a4c8503b3f3 100644
--- a/homeassistant/components/emulated_roku/.translations/es.json
+++ b/homeassistant/components/emulated_roku/.translations/es.json
@@ -12,6 +12,7 @@
},
"title": "Definir la configuraci\u00f3n del servidor"
}
- }
+ },
+ "title": "EmulatedRoku"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/emulated_roku/__init__.py b/homeassistant/components/emulated_roku/__init__.py
index ef87e14ec43..72d4dff72db 100644
--- a/homeassistant/components/emulated_roku/__init__.py
+++ b/homeassistant/components/emulated_roku/__init__.py
@@ -11,8 +11,6 @@ from .const import (
CONF_ADVERTISE_IP, CONF_ADVERTISE_PORT, CONF_HOST_IP, CONF_LISTEN_PORT,
CONF_SERVERS, CONF_UPNP_BIND_MULTICAST, DOMAIN)
-REQUIREMENTS = ['emulated_roku==0.1.8']
-
SERVER_CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_LISTEN_PORT): cv.port,
diff --git a/homeassistant/components/emulated_roku/manifest.json b/homeassistant/components/emulated_roku/manifest.json
new file mode 100644
index 00000000000..3b8eba396ec
--- /dev/null
+++ b/homeassistant/components/emulated_roku/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "emulated_roku",
+ "name": "Emulated roku",
+ "documentation": "https://www.home-assistant.io/components/emulated_roku",
+ "requirements": [
+ "emulated_roku==0.1.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/enigma2/manifest.json b/homeassistant/components/enigma2/manifest.json
new file mode 100644
index 00000000000..d523bd72b72
--- /dev/null
+++ b/homeassistant/components/enigma2/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "enigma2",
+ "name": "Enigma2",
+ "documentation": "https://www.home-assistant.io/components/enigma2",
+ "requirements": [
+ "openwebifpy==3.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py
index 0b6f995be97..4662c707637 100644
--- a/homeassistant/components/enigma2/media_player.py
+++ b/homeassistant/components/enigma2/media_player.py
@@ -14,8 +14,6 @@ from homeassistant.const import (
STATE_OFF, STATE_ON, STATE_PLAYING, CONF_PORT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['openwebifpy==3.1.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MEDIA_CURRENTLY_RECORDING = 'media_currently_recording'
diff --git a/homeassistant/components/enocean/__init__.py b/homeassistant/components/enocean/__init__.py
index 8b3c27025cd..2dcf6a3a0ac 100644
--- a/homeassistant/components/enocean/__init__.py
+++ b/homeassistant/components/enocean/__init__.py
@@ -6,8 +6,6 @@ import voluptuous as vol
from homeassistant.const import CONF_DEVICE
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['enocean==0.40']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'enocean'
diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py
index 1fde8c79e40..649bec024e3 100644
--- a/homeassistant/components/enocean/binary_sensor.py
+++ b/homeassistant/components/enocean/binary_sensor.py
@@ -12,7 +12,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['enocean']
DEFAULT_NAME = 'EnOcean binary sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py
index f574f89f951..9ec3f4ab27b 100644
--- a/homeassistant/components/enocean/light.py
+++ b/homeassistant/components/enocean/light.py
@@ -15,8 +15,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_SENDER_ID = 'sender_id'
DEFAULT_NAME = 'EnOcean Light'
-DEPENDENCIES = ['enocean']
-
SUPPORT_ENOCEAN = SUPPORT_BRIGHTNESS
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/enocean/manifest.json b/homeassistant/components/enocean/manifest.json
new file mode 100644
index 00000000000..7c4d7c0b8d9
--- /dev/null
+++ b/homeassistant/components/enocean/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "enocean",
+ "name": "Enocean",
+ "documentation": "https://www.home-assistant.io/components/enocean",
+ "requirements": [
+ "enocean==0.40"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py
index 8d79de2c50d..530738e1f88 100644
--- a/homeassistant/components/enocean/sensor.py
+++ b/homeassistant/components/enocean/sensor.py
@@ -12,8 +12,6 @@ from homeassistant.components import enocean
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'EnOcean sensor'
-DEPENDENCIES = ['enocean']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py
index 4dfbafd36b1..f0b132c9d1c 100644
--- a/homeassistant/components/enocean/switch.py
+++ b/homeassistant/components/enocean/switch.py
@@ -12,7 +12,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'EnOcean Switch'
-DEPENDENCIES = ['enocean']
CONF_CHANNEL = 'channel'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json
new file mode 100644
index 00000000000..6fee88b39fc
--- /dev/null
+++ b/homeassistant/components/enphase_envoy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "enphase_envoy",
+ "name": "Enphase envoy",
+ "documentation": "https://www.home-assistant.io/components/enphase_envoy",
+ "requirements": [
+ "envoy_reader==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py
index 1bfee88d41c..7077e12d750 100644
--- a/homeassistant/components/enphase_envoy/sensor.py
+++ b/homeassistant/components/enphase_envoy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Enphase Envoy solar energy monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.enphase_envoy/
-"""
+"""Support for Enphase Envoy solar energy monitor."""
import logging
import voluptuous as vol
@@ -15,7 +10,6 @@ from homeassistant.const import (
CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, POWER_WATT)
-REQUIREMENTS = ['envoy_reader==0.3']
_LOGGER = logging.getLogger(__name__)
SENSORS = {
diff --git a/homeassistant/components/entur_public_transport/manifest.json b/homeassistant/components/entur_public_transport/manifest.json
new file mode 100644
index 00000000000..b2b60cff95a
--- /dev/null
+++ b/homeassistant/components/entur_public_transport/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "entur_public_transport",
+ "name": "Entur public transport",
+ "documentation": "https://www.home-assistant.io/components/entur_public_transport",
+ "requirements": [
+ "enturclient==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py
index b2e22867690..61b183b9408 100644
--- a/homeassistant/components/entur_public_transport/sensor.py
+++ b/homeassistant/components/entur_public_transport/sensor.py
@@ -14,8 +14,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['enturclient==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
API_CLIENT_NAME = 'homeassistant-homeassistant'
diff --git a/homeassistant/components/envirophat/manifest.json b/homeassistant/components/envirophat/manifest.json
new file mode 100644
index 00000000000..c69a66d43f8
--- /dev/null
+++ b/homeassistant/components/envirophat/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "envirophat",
+ "name": "Envirophat",
+ "documentation": "https://www.home-assistant.io/components/envirophat",
+ "requirements": [
+ "envirophat==0.0.6",
+ "smbus-cffi==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/envirophat/sensor.py b/homeassistant/components/envirophat/sensor.py
index 7683c06b69c..6d792df2421 100644
--- a/homeassistant/components/envirophat/sensor.py
+++ b/homeassistant/components/envirophat/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Enviro pHAT sensors.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.envirophat
-"""
+"""Support for Enviro pHAT sensors."""
import importlib
import logging
from datetime import timedelta
@@ -16,9 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['envirophat==0.0.6',
- 'smbus-cffi==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'envirophat'
diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py
index c46a26c6f85..d7a015e8e45 100644
--- a/homeassistant/components/envisalink/__init__.py
+++ b/homeassistant/components/envisalink/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['pyenvisalink==3.8']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py
index 44874c6d5e8..91a59d8f842 100644
--- a/homeassistant/components/envisalink/alarm_control_panel.py
+++ b/homeassistant/components/envisalink/alarm_control_panel.py
@@ -18,8 +18,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['envisalink']
-
SERVICE_ALARM_KEYPRESS = 'envisalink_alarm_keypress'
ATTR_KEYPRESS = 'keypress'
ALARM_KEYPRESS_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py
index 267bba8cd28..bf47749d228 100644
--- a/homeassistant/components/envisalink/binary_sensor.py
+++ b/homeassistant/components/envisalink/binary_sensor.py
@@ -14,8 +14,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['envisalink']
-
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/envisalink/manifest.json b/homeassistant/components/envisalink/manifest.json
new file mode 100644
index 00000000000..b34aa08951c
--- /dev/null
+++ b/homeassistant/components/envisalink/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "envisalink",
+ "name": "Envisalink",
+ "documentation": "https://www.home-assistant.io/components/envisalink",
+ "requirements": [
+ "pyenvisalink==3.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py
index 67a601b02a2..2652a7e2137 100644
--- a/homeassistant/components/envisalink/sensor.py
+++ b/homeassistant/components/envisalink/sensor.py
@@ -11,8 +11,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['envisalink']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/ephember/climate.py b/homeassistant/components/ephember/climate.py
index 220c073ef80..4e741dacf9d 100644
--- a/homeassistant/components/ephember/climate.py
+++ b/homeassistant/components/ephember/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for the EPH Controls Ember themostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.ephember/
-"""
+"""Support for the EPH Controls Ember themostats."""
import logging
from datetime import timedelta
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.const import (
ATTR_TEMPERATURE, TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, STATE_OFF)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyephember==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
diff --git a/homeassistant/components/ephember/manifest.json b/homeassistant/components/ephember/manifest.json
new file mode 100644
index 00000000000..3fed307aed5
--- /dev/null
+++ b/homeassistant/components/ephember/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ephember",
+ "name": "Ephember",
+ "documentation": "https://www.home-assistant.io/components/ephember",
+ "requirements": [
+ "pyephember==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ttroy50"
+ ]
+}
diff --git a/homeassistant/components/epson/manifest.json b/homeassistant/components/epson/manifest.json
new file mode 100644
index 00000000000..e6623b83013
--- /dev/null
+++ b/homeassistant/components/epson/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "epson",
+ "name": "Epson",
+ "documentation": "https://www.home-assistant.io/components/epson",
+ "requirements": [
+ "epson-projector==0.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py
index 38c0ffacc32..8273ca9a21a 100644
--- a/homeassistant/components/epson/media_player.py
+++ b/homeassistant/components/epson/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Epson projector.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/media_player.epson/
-"""
+"""Support for Epson projector."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.const import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['epson-projector==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CMODE = 'cmode'
@@ -142,12 +135,14 @@ class EpsonProjector(MediaPlayerDevice):
async def async_turn_on(self):
"""Turn on epson."""
from epson_projector.const import TURN_ON
- await self._projector.send_command(TURN_ON)
+ if self._state == STATE_OFF:
+ await self._projector.send_command(TURN_ON)
async def async_turn_off(self):
"""Turn off epson."""
from epson_projector.const import TURN_OFF
- await self._projector.send_command(TURN_OFF)
+ if self._state == STATE_ON:
+ await self._projector.send_command(TURN_OFF)
@property
def source_list(self):
diff --git a/homeassistant/components/epson/services.yaml b/homeassistant/components/epson/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/epsonworkforce/__init__.py b/homeassistant/components/epsonworkforce/__init__.py
new file mode 100644
index 00000000000..5efd217b1dd
--- /dev/null
+++ b/homeassistant/components/epsonworkforce/__init__.py
@@ -0,0 +1 @@
+"""The epsonworkforce component."""
diff --git a/homeassistant/components/epsonworkforce/manifest.json b/homeassistant/components/epsonworkforce/manifest.json
new file mode 100644
index 00000000000..5f39c414725
--- /dev/null
+++ b/homeassistant/components/epsonworkforce/manifest.json
@@ -0,0 +1,9 @@
+{
+ "domain": "epsonworkforce",
+ "name": "Epson Workforce",
+ "documentation": "https://www.home-assistant.io/components/epsonworkforce",
+ "dependencies": [],
+ "codeowners": ["@ThaStealth"],
+ "requirements": ["epsonprinter==0.0.8"]
+}
+
diff --git a/homeassistant/components/epsonworkforce/sensor.py b/homeassistant/components/epsonworkforce/sensor.py
new file mode 100644
index 00000000000..6abf04d8aaa
--- /dev/null
+++ b/homeassistant/components/epsonworkforce/sensor.py
@@ -0,0 +1,85 @@
+"""Support for Epson Workforce Printer."""
+from datetime import timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import CONF_HOST, CONF_MONITORED_CONDITIONS
+from homeassistant.exceptions import PlatformNotReady
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.entity import Entity
+
+REQUIREMENTS = ['epsonprinter==0.0.8']
+
+_LOGGER = logging.getLogger(__name__)
+MONITORED_CONDITIONS = {
+ 'black': ['Inklevel Black', '%', 'mdi:water'],
+ 'magenta': ['Inklevel Magenta', '%', 'mdi:water'],
+ 'cyan': ['Inklevel Cyan', '%', 'mdi:water'],
+ 'yellow': ['Inklevel Yellow', '%', 'mdi:water'],
+ 'clean': ['Inklevel Cleaning', '%', 'mdi:water'],
+}
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_MONITORED_CONDITIONS):
+ vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]),
+})
+SCAN_INTERVAL = timedelta(minutes=60)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the cartridge sensor."""
+ host = config.get(CONF_HOST)
+
+ from epsonprinter_pkg.epsonprinterapi import EpsonPrinterAPI
+ api = EpsonPrinterAPI(host)
+ if not api.available:
+ raise PlatformNotReady()
+
+ sensors = [EpsonPrinterCartridge(api, condition)
+ for condition in config[CONF_MONITORED_CONDITIONS]]
+
+ add_devices(sensors, True)
+
+
+class EpsonPrinterCartridge(Entity):
+ """Representation of a cartridge sensor."""
+
+ def __init__(self, api, cartridgeidx):
+ """Initialize a cartridge sensor."""
+ self._api = api
+
+ self._id = cartridgeidx
+ self._name = MONITORED_CONDITIONS[self._id][0]
+ self._unit = MONITORED_CONDITIONS[self._id][1]
+ self._icon = MONITORED_CONDITIONS[self._id][2]
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @property
+ def icon(self):
+ """Icon to use in the frontend, if any."""
+ return self._icon
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit the value is expressed in."""
+ return self._unit
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ return self._api.getSensorValue(self._id)
+
+ @property
+ def available(self):
+ """Could the device be accessed during the last update call."""
+ return self._api.available
+
+ def update(self):
+ """Get the latest data from the Epson printer."""
+ self._api.update()
diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py
index 43a26c27ce1..fc12438fcf3 100644
--- a/homeassistant/components/eq3btsmart/climate.py
+++ b/homeassistant/components/eq3btsmart/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for eQ-3 Bluetooth Smart thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.eq3btsmart/
-"""
+"""Support for eQ-3 Bluetooth Smart thermostats."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from homeassistant.const import (
TEMP_CELSIUS, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
STATE_BOOST = 'boost'
diff --git a/homeassistant/components/eq3btsmart/manifest.json b/homeassistant/components/eq3btsmart/manifest.json
new file mode 100644
index 00000000000..6d13c79bcec
--- /dev/null
+++ b/homeassistant/components/eq3btsmart/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "eq3btsmart",
+ "name": "Eq3btsmart",
+ "documentation": "https://www.home-assistant.io/components/eq3btsmart",
+ "requirements": [
+ "construct==2.9.45",
+ "python-eq3bt==0.1.9"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@rytilahti"
+ ]
+}
diff --git a/homeassistant/components/esphome/.translations/es.json b/homeassistant/components/esphome/.translations/es.json
index c4b18899eaf..ea79edc0b31 100644
--- a/homeassistant/components/esphome/.translations/es.json
+++ b/homeassistant/components/esphome/.translations/es.json
@@ -14,6 +14,10 @@
"description": "Escribe la contrase\u00f1a que hayas establecido en tu configuraci\u00f3n.",
"title": "Escribe la contrase\u00f1a"
},
+ "discovery_confirm": {
+ "description": "\u00bfQuieres a\u00f1adir el nodo `{name}` de ESPHome a Home Assistant?",
+ "title": "Nodo ESPHome descubierto"
+ },
"user": {
"data": {
"host": "Host",
diff --git a/homeassistant/components/esphome/.translations/nn.json b/homeassistant/components/esphome/.translations/nn.json
new file mode 100644
index 00000000000..830391f58f6
--- /dev/null
+++ b/homeassistant/components/esphome/.translations/nn.json
@@ -0,0 +1,9 @@
+{
+ "config": {
+ "step": {
+ "discovery_confirm": {
+ "title": "Fann ESPhome node"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/esphome/.translations/no.json b/homeassistant/components/esphome/.translations/no.json
index 095e8825fbd..c71424b6f00 100644
--- a/homeassistant/components/esphome/.translations/no.json
+++ b/homeassistant/components/esphome/.translations/no.json
@@ -13,7 +13,7 @@
"data": {
"password": "Passord"
},
- "description": "Vennligst skriv inn passordet du har angitt i din konfigurasjon.",
+ "description": "Vennligst skriv inn passordet du har angitt i din konfigurasjon for {name}.",
"title": "Skriv Inn Passord"
},
"discovery_confirm": {
diff --git a/homeassistant/components/esphome/.translations/pl.json b/homeassistant/components/esphome/.translations/pl.json
index d24fb929068..5693efde9a8 100644
--- a/homeassistant/components/esphome/.translations/pl.json
+++ b/homeassistant/components/esphome/.translations/pl.json
@@ -18,7 +18,7 @@
},
"discovery_confirm": {
"description": "Czy chcesz doda\u0107 w\u0119ze\u0142 ESPHome `{name}` do Home Assistant?",
- "title": "Znaleziono w\u0119ze\u0142 ESPHome "
+ "title": "Znaleziono w\u0119ze\u0142 ESPHome"
},
"user": {
"data": {
diff --git a/homeassistant/components/esphome/.translations/pt.json b/homeassistant/components/esphome/.translations/pt.json
index ea1e25c3024..7e4a85f3514 100644
--- a/homeassistant/components/esphome/.translations/pt.json
+++ b/homeassistant/components/esphome/.translations/pt.json
@@ -13,7 +13,7 @@
"data": {
"password": "Palavra-passe"
},
- "description": "Por favor, insira a palavra-passe que colocou na configura\u00e7\u00e3o",
+ "description": "Por favor, insira a palavra-passe que colocou na configura\u00e7\u00e3o para {name}",
"title": "Palavra-passe"
},
"user": {
diff --git a/homeassistant/components/esphome/.translations/uk.json b/homeassistant/components/esphome/.translations/uk.json
index 4f4c2f32c61..79c9e70bcc8 100644
--- a/homeassistant/components/esphome/.translations/uk.json
+++ b/homeassistant/components/esphome/.translations/uk.json
@@ -18,7 +18,7 @@
},
"discovery_confirm": {
"description": "\u0414\u043e\u0434\u0430\u0442\u0438 ESPHome \u0432\u0443\u0437\u043e\u043b {name} \u0443 Home Assistant?",
- "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0432\u0443\u0437\u043e\u043b ESPHome "
+ "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0432\u0443\u0437\u043e\u043b ESPHome"
},
"user": {
"data": {
diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py
index 39422c530b3..e5feedd8421 100644
--- a/homeassistant/components/esphome/__init__.py
+++ b/homeassistant/components/esphome/__init__.py
@@ -1,6 +1,7 @@
"""Support for esphome devices."""
import asyncio
import logging
+import math
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Callable, Tuple
import attr
@@ -32,8 +33,6 @@ if TYPE_CHECKING:
ServiceCall, UserService
DOMAIN = 'esphome'
-REQUIREMENTS = ['aioesphomeapi==1.7.0']
-
_LOGGER = logging.getLogger(__name__)
DISPATCHER_UPDATE_ENTITY = 'esphome_{entry_id}_update_{component_key}_{key}'
@@ -49,6 +48,7 @@ STORAGE_VERSION = 1
HA_COMPONENTS = [
'binary_sensor',
'camera',
+ 'climate',
'cover',
'fan',
'light',
@@ -149,7 +149,8 @@ class RuntimeEntryData:
def _attr_obj_from_dict(cls, **kwargs):
- return cls(**{key: kwargs[key] for key in attr.fields_dict(cls)})
+ return cls(**{key: kwargs[key] for key in attr.fields_dict(cls)
+ if key in kwargs})
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
@@ -381,16 +382,15 @@ async def _async_setup_device_registry(hass: HomeAssistantType,
async def _register_service(hass: HomeAssistantType,
entry_data: RuntimeEntryData,
service: 'UserService'):
- from aioesphomeapi import USER_SERVICE_ARG_BOOL, USER_SERVICE_ARG_INT, \
- USER_SERVICE_ARG_FLOAT, USER_SERVICE_ARG_STRING
+ from aioesphomeapi import UserServiceArgType
service_name = '{}_{}'.format(entry_data.device_info.name, service.name)
schema = {}
for arg in service.args:
schema[vol.Required(arg.name)] = {
- USER_SERVICE_ARG_BOOL: cv.boolean,
- USER_SERVICE_ARG_INT: vol.Coerce(int),
- USER_SERVICE_ARG_FLOAT: vol.Coerce(float),
- USER_SERVICE_ARG_STRING: cv.string,
+ UserServiceArgType.BOOL: cv.boolean,
+ UserServiceArgType.INT: vol.Coerce(int),
+ UserServiceArgType.FLOAT: vol.Coerce(float),
+ UserServiceArgType.STRING: cv.string,
}[arg.type_]
async def execute_service(call):
@@ -522,6 +522,51 @@ async def platform_async_setup_entry(hass: HomeAssistantType,
)
+def esphome_state_property(func):
+ """Wrap a state property of an esphome entity.
+
+ This checks if the state object in the entity is set, and
+ prevents writing NAN values to the Home Assistant state machine.
+ """
+ @property
+ def _wrapper(self):
+ if self._state is None:
+ return None
+ val = func(self)
+ if isinstance(val, float) and math.isnan(val):
+ # Home Assistant doesn't use NAN values in state machine
+ # (not JSON serializable)
+ return None
+ return val
+ return _wrapper
+
+
+class EsphomeEnumMapper:
+ """Helper class to convert between hass and esphome enum values."""
+
+ def __init__(self, func: Callable[[], Dict[int, str]]):
+ """Construct a EsphomeEnumMapper."""
+ self._func = func
+
+ def from_esphome(self, value: int) -> str:
+ """Convert from an esphome int representation to a hass string."""
+ return self._func()[value]
+
+ def from_hass(self, value: str) -> int:
+ """Convert from a hass string to a esphome int representation."""
+ inverse = {v: k for k, v in self._func().items()}
+ return inverse[value]
+
+
+def esphome_map_enum(func: Callable[[], Dict[int, str]]):
+ """Map esphome int enum values to hass string constants.
+
+ This class has to be used as a decorator. This ensures the aioesphomeapi
+ import is only happening at runtime.
+ """
+ return EsphomeEnumMapper(func)
+
+
class EsphomeEntity(Entity):
"""Define a generic esphome entity."""
@@ -557,11 +602,11 @@ class EsphomeEntity(Entity):
self.async_schedule_update_ha_state)
)
- async def _on_update(self):
+ async def _on_update(self) -> None:
"""Update the entity state when state or static info changed."""
self.async_schedule_update_ha_state()
- async def async_will_remove_from_hass(self):
+ async def async_will_remove_from_hass(self) -> None:
"""Unregister callbacks."""
for remove_callback in self._remove_callbacks:
remove_callback()
@@ -610,7 +655,7 @@ class EsphomeEntity(Entity):
return self._static_info.unique_id
@property
- def device_info(self):
+ def device_info(self) -> Dict[str, Any]:
"""Return device registry information for this entity."""
return {
'connections': {(dr.CONNECTION_NETWORK_MAC,
diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py
index 2db2f209fa5..6a6f9bfac1c 100644
--- a/homeassistant/components/esphome/binary_sensor.py
+++ b/homeassistant/components/esphome/binary_sensor.py
@@ -10,7 +10,6 @@ if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import BinarySensorInfo, BinarySensorState # noqa
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -39,7 +38,7 @@ class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice):
return super()._state
@property
- def is_on(self):
+ def is_on(self) -> Optional[bool]:
"""Return true if the binary sensor is on."""
if self._static_info.is_status_binary_sensor:
# Status binary sensors indicated connected state.
@@ -50,12 +49,12 @@ class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice):
return self._state.state
@property
- def device_class(self):
+ def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._static_info.device_class
@property
- def available(self):
+ def available(self) -> bool:
"""Return True if entity is available."""
if self._static_info.is_status_binary_sensor:
return True
diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py
index 319a2c2a4d9..64e73dc8784 100644
--- a/homeassistant/components/esphome/camera.py
+++ b/homeassistant/components/esphome/camera.py
@@ -13,7 +13,6 @@ if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import CameraInfo, CameraState # noqa
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -48,7 +47,7 @@ class EsphomeCamera(Camera, EsphomeEntity):
def _state(self) -> Optional['CameraState']:
return super()._state
- async def _on_update(self):
+ async def _on_update(self) -> None:
"""Notify listeners of new image when update arrives."""
await super()._on_update()
async with self._image_cond:
diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py
new file mode 100644
index 00000000000..184eb4b6270
--- /dev/null
+++ b/homeassistant/components/esphome/climate.py
@@ -0,0 +1,172 @@
+"""Support for ESPHome climate devices."""
+import logging
+from typing import TYPE_CHECKING, List, Optional
+
+from homeassistant.components.climate import ClimateDevice
+from homeassistant.components.climate.const import (
+ ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
+ STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_AWAY_MODE,
+ SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
+ SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
+from homeassistant.const import (
+ ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE,
+ STATE_OFF, TEMP_CELSIUS)
+
+from . import EsphomeEntity, platform_async_setup_entry, \
+ esphome_state_property, esphome_map_enum
+
+if TYPE_CHECKING:
+ # pylint: disable=unused-import
+ from aioesphomeapi import ClimateInfo, ClimateState, ClimateMode # noqa
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(hass, entry, async_add_entities):
+ """Set up ESPHome climate devices based on a config entry."""
+ # pylint: disable=redefined-outer-name
+ from aioesphomeapi import ClimateInfo, ClimateState # noqa
+
+ await platform_async_setup_entry(
+ hass, entry, async_add_entities,
+ component_key='climate',
+ info_type=ClimateInfo, entity_type=EsphomeClimateDevice,
+ state_type=ClimateState
+ )
+
+
+@esphome_map_enum
+def _climate_modes():
+ # pylint: disable=redefined-outer-name
+ from aioesphomeapi import ClimateMode # noqa
+ return {
+ ClimateMode.OFF: STATE_OFF,
+ ClimateMode.AUTO: STATE_AUTO,
+ ClimateMode.COOL: STATE_COOL,
+ ClimateMode.HEAT: STATE_HEAT,
+ }
+
+
+class EsphomeClimateDevice(EsphomeEntity, ClimateDevice):
+ """A climate implementation for ESPHome."""
+
+ @property
+ def _static_info(self) -> 'ClimateInfo':
+ return super()._static_info
+
+ @property
+ def _state(self) -> Optional['ClimateState']:
+ return super()._state
+
+ @property
+ def precision(self) -> float:
+ """Return the precision of the climate device."""
+ precicions = [PRECISION_WHOLE, PRECISION_HALVES, PRECISION_TENTHS]
+ for prec in precicions:
+ if self._static_info.visual_temperature_step >= prec:
+ return prec
+ # Fall back to highest precision, tenths
+ return PRECISION_TENTHS
+
+ @property
+ def temperature_unit(self) -> str:
+ """Return the unit of measurement used by the platform."""
+ return TEMP_CELSIUS
+
+ @property
+ def operation_list(self) -> List[str]:
+ """Return the list of available operation modes."""
+ return [
+ _climate_modes.from_esphome(mode)
+ for mode in self._static_info.supported_modes
+ ]
+
+ @property
+ def target_temperature_step(self) -> float:
+ """Return the supported step of target temperature."""
+ # Round to one digit because of floating point math
+ return round(self._static_info.visual_temperature_step, 1)
+
+ @property
+ def min_temp(self) -> float:
+ """Return the minimum temperature."""
+ return self._static_info.visual_min_temperature
+
+ @property
+ def max_temp(self) -> float:
+ """Return the maximum temperature."""
+ return self._static_info.visual_max_temperature
+
+ @property
+ def supported_features(self) -> int:
+ """Return the list of supported features."""
+ features = SUPPORT_OPERATION_MODE
+ if self._static_info.supports_two_point_target_temperature:
+ features |= (SUPPORT_TARGET_TEMPERATURE_LOW |
+ SUPPORT_TARGET_TEMPERATURE_HIGH)
+ else:
+ features |= SUPPORT_TARGET_TEMPERATURE
+ if self._static_info.supports_away:
+ features |= SUPPORT_AWAY_MODE
+ return features
+
+ @esphome_state_property
+ def current_operation(self) -> Optional[str]:
+ """Return current operation ie. heat, cool, idle."""
+ return _climate_modes.from_esphome(self._state.mode)
+
+ @esphome_state_property
+ def current_temperature(self) -> Optional[float]:
+ """Return the current temperature."""
+ return self._state.current_temperature
+
+ @esphome_state_property
+ def target_temperature(self) -> Optional[float]:
+ """Return the temperature we try to reach."""
+ return self._state.target_temperature
+
+ @esphome_state_property
+ def target_temperature_low(self) -> Optional[float]:
+ """Return the lowbound target temperature we try to reach."""
+ return self._state.target_temperature_low
+
+ @esphome_state_property
+ def target_temperature_high(self) -> Optional[float]:
+ """Return the highbound target temperature we try to reach."""
+ return self._state.target_temperature_high
+
+ @esphome_state_property
+ def is_away_mode_on(self) -> Optional[bool]:
+ """Return true if away mode is on."""
+ return self._state.away
+
+ async def async_set_temperature(self, **kwargs) -> None:
+ """Set new target temperature (and operation mode if set)."""
+ data = {'key': self._static_info.key}
+ if ATTR_OPERATION_MODE in kwargs:
+ data['mode'] = _climate_modes.from_hass(
+ kwargs[ATTR_OPERATION_MODE])
+ if ATTR_TEMPERATURE in kwargs:
+ data['target_temperature'] = kwargs[ATTR_TEMPERATURE]
+ if ATTR_TARGET_TEMP_LOW in kwargs:
+ data['target_temperature_low'] = kwargs[ATTR_TARGET_TEMP_LOW]
+ if ATTR_TARGET_TEMP_HIGH in kwargs:
+ data['target_temperature_high'] = kwargs[ATTR_TARGET_TEMP_HIGH]
+ await self._client.climate_command(**data)
+
+ async def async_set_operation_mode(self, operation_mode) -> None:
+ """Set new target operation mode."""
+ await self._client.climate_command(
+ key=self._static_info.key,
+ mode=_climate_modes.from_hass(operation_mode),
+ )
+
+ async def async_turn_away_mode_on(self) -> None:
+ """Turn away mode on."""
+ await self._client.climate_command(key=self._static_info.key,
+ away=True)
+
+ async def async_turn_away_mode_off(self) -> None:
+ """Turn away mode off."""
+ await self._client.climate_command(key=self._static_info.key,
+ away=False)
diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py
index d86c40e627e..a3ef15fa4c7 100644
--- a/homeassistant/components/esphome/cover.py
+++ b/homeassistant/components/esphome/cover.py
@@ -3,17 +3,18 @@ import logging
from typing import TYPE_CHECKING, Optional
from homeassistant.components.cover import (
- SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, CoverDevice)
+ ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT,
+ SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION,
+ SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, CoverDevice)
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
-from . import EsphomeEntity, platform_async_setup_entry
+from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import CoverInfo, CoverState # noqa
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -38,44 +39,91 @@ class EsphomeCover(EsphomeEntity, CoverDevice):
def _static_info(self) -> 'CoverInfo':
return super()._static_info
- @property
- def _state(self) -> Optional['CoverState']:
- return super()._state
-
@property
def supported_features(self) -> int:
"""Flag supported features."""
- return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
+ flags = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
+ if self._static_info.supports_position:
+ flags |= SUPPORT_SET_POSITION
+ if self._static_info.supports_tilt:
+ flags |= (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT |
+ SUPPORT_SET_TILT_POSITION)
+ return flags
+
+ @property
+ def device_class(self) -> str:
+ """Return the class of this device, from component DEVICE_CLASSES."""
+ return self._static_info.device_class
@property
def assumed_state(self) -> bool:
"""Return true if we do optimistic updates."""
- return self._static_info.is_optimistic
+ return self._static_info.assumed_state
@property
+ def _state(self) -> Optional['CoverState']:
+ return super()._state
+
+ @esphome_state_property
def is_closed(self) -> Optional[bool]:
"""Return if the cover is closed or not."""
- if self._state is None:
+ # Check closed state with api version due to a protocol change
+ return self._state.is_closed(self._client.api_version)
+
+ @esphome_state_property
+ def is_opening(self) -> bool:
+ """Return if the cover is opening or not."""
+ from aioesphomeapi import CoverOperation
+ return self._state.current_operation == CoverOperation.IS_OPENING
+
+ @esphome_state_property
+ def is_closing(self) -> bool:
+ """Return if the cover is closing or not."""
+ from aioesphomeapi import CoverOperation
+ return self._state.current_operation == CoverOperation.IS_CLOSING
+
+ @esphome_state_property
+ def current_cover_position(self) -> Optional[float]:
+ """Return current position of cover. 0 is closed, 100 is open."""
+ if not self._static_info.supports_position:
return None
- return bool(self._state.state)
+ return self._state.position * 100.0
+
+ @esphome_state_property
+ def current_cover_tilt_position(self) -> Optional[float]:
+ """Return current position of cover tilt. 0 is closed, 100 is open."""
+ if not self._static_info.supports_tilt:
+ return None
+ return self._state.tilt * 100.0
async def async_open_cover(self, **kwargs) -> None:
"""Open the cover."""
- from aioesphomeapi.client import COVER_COMMAND_OPEN
-
await self._client.cover_command(key=self._static_info.key,
- command=COVER_COMMAND_OPEN)
+ position=1.0)
async def async_close_cover(self, **kwargs) -> None:
"""Close cover."""
- from aioesphomeapi.client import COVER_COMMAND_CLOSE
-
await self._client.cover_command(key=self._static_info.key,
- command=COVER_COMMAND_CLOSE)
+ position=0.0)
- async def async_stop_cover(self, **kwargs):
+ async def async_stop_cover(self, **kwargs) -> None:
"""Stop the cover."""
- from aioesphomeapi.client import COVER_COMMAND_STOP
+ await self._client.cover_command(key=self._static_info.key, stop=True)
+ async def async_set_cover_position(self, **kwargs) -> None:
+ """Move the cover to a specific position."""
await self._client.cover_command(key=self._static_info.key,
- command=COVER_COMMAND_STOP)
+ position=kwargs[ATTR_POSITION] / 100)
+
+ async def async_open_cover_tilt(self, **kwargs) -> None:
+ """Open the cover tilt."""
+ await self._client.cover_command(key=self._static_info.key, tilt=1.0)
+
+ async def async_close_cover_tilt(self, **kwargs) -> None:
+ """Close the cover tilt."""
+ await self._client.cover_command(key=self._static_info.key, tilt=0.0)
+
+ async def async_set_cover_tilt_position(self, **kwargs) -> None:
+ """Move the cover tilt to a specific position."""
+ await self._client.cover_command(key=self._static_info.key,
+ tilt=kwargs[ATTR_TILT_POSITION] / 100)
diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py
index 05f18cb014a..50cf04203f3 100644
--- a/homeassistant/components/esphome/fan.py
+++ b/homeassistant/components/esphome/fan.py
@@ -8,13 +8,13 @@ from homeassistant.components.fan import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
-from . import EsphomeEntity, platform_async_setup_entry
+from . import EsphomeEntity, platform_async_setup_entry, \
+ esphome_state_property, esphome_map_enum
if TYPE_CHECKING:
# pylint: disable=unused-import
- from aioesphomeapi import FanInfo, FanState # noqa
+ from aioesphomeapi import FanInfo, FanState, FanSpeed # noqa
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -32,12 +32,15 @@ async def async_setup_entry(hass: HomeAssistantType,
)
-FAN_SPEED_STR_TO_INT = {
- SPEED_LOW: 0,
- SPEED_MEDIUM: 1,
- SPEED_HIGH: 2
-}
-FAN_SPEED_INT_TO_STR = {v: k for k, v in FAN_SPEED_STR_TO_INT.items()}
+@esphome_map_enum
+def _fan_speeds():
+ # pylint: disable=redefined-outer-name
+ from aioesphomeapi import FanSpeed # noqa
+ return {
+ FanSpeed.LOW: SPEED_LOW,
+ FanSpeed.MEDIUM: SPEED_MEDIUM,
+ FanSpeed.HIGH: SPEED_HIGH,
+ }
class EsphomeFan(EsphomeEntity, FanEntity):
@@ -56,8 +59,9 @@ class EsphomeFan(EsphomeEntity, FanEntity):
if speed == SPEED_OFF:
await self.async_turn_off()
return
+
await self._client.fan_command(
- self._static_info.key, speed=FAN_SPEED_STR_TO_INT[speed])
+ self._static_info.key, speed=_fan_speeds.from_hass(speed))
async def async_turn_on(self, speed: Optional[str] = None,
**kwargs) -> None:
@@ -67,7 +71,7 @@ class EsphomeFan(EsphomeEntity, FanEntity):
return
data = {'key': self._static_info.key, 'state': True}
if speed is not None:
- data['speed'] = FAN_SPEED_STR_TO_INT[speed]
+ data['speed'] = _fan_speeds.from_hass(speed)
await self._client.fan_command(**data)
# pylint: disable=arguments-differ
@@ -75,32 +79,26 @@ class EsphomeFan(EsphomeEntity, FanEntity):
"""Turn off the fan."""
await self._client.fan_command(key=self._static_info.key, state=False)
- async def async_oscillate(self, oscillating: bool):
+ async def async_oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
await self._client.fan_command(key=self._static_info.key,
oscillating=oscillating)
- @property
+ @esphome_state_property
def is_on(self) -> Optional[bool]:
"""Return true if the entity is on."""
- if self._state is None:
- return None
return self._state.state
- @property
+ @esphome_state_property
def speed(self) -> Optional[str]:
"""Return the current speed."""
- if self._state is None:
- return None
if not self._static_info.supports_speed:
return None
- return FAN_SPEED_INT_TO_STR[self._state.speed]
+ return _fan_speeds.from_esphome(self._state.speed)
- @property
+ @esphome_state_property
def oscillating(self) -> None:
"""Return the oscillation state."""
- if self._state is None:
- return None
if not self._static_info.supports_oscillation:
return None
return self._state.oscillating
diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py
index c84c50010d9..6b4abafe62b 100644
--- a/homeassistant/components/esphome/light.py
+++ b/homeassistant/components/esphome/light.py
@@ -11,13 +11,12 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
import homeassistant.util.color as color_util
-from . import EsphomeEntity, platform_async_setup_entry
+from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import LightInfo, LightState # noqa
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -52,11 +51,9 @@ class EsphomeLight(EsphomeEntity, Light):
def _state(self) -> Optional['LightState']:
return super()._state
- @property
+ @esphome_state_property
def is_on(self) -> Optional[bool]:
"""Return true if the switch is on."""
- if self._state is None:
- return None
return self._state.state
async def async_turn_on(self, **kwargs) -> None:
@@ -89,42 +86,32 @@ class EsphomeLight(EsphomeEntity, Light):
data['transition_length'] = kwargs[ATTR_TRANSITION]
await self._client.light_command(**data)
- @property
+ @esphome_state_property
def brightness(self) -> Optional[int]:
"""Return the brightness of this light between 0..255."""
- if self._state is None:
- return None
return round(self._state.brightness * 255)
- @property
+ @esphome_state_property
def hs_color(self) -> Optional[Tuple[float, float]]:
"""Return the hue and saturation color value [float, float]."""
- if self._state is None:
- return None
return color_util.color_RGB_to_hs(
self._state.red * 255,
self._state.green * 255,
self._state.blue * 255)
- @property
+ @esphome_state_property
def color_temp(self) -> Optional[float]:
"""Return the CT color value in mireds."""
- if self._state is None:
- return None
return self._state.color_temperature
- @property
+ @esphome_state_property
def white_value(self) -> Optional[int]:
"""Return the white value of this light between 0..255."""
- if self._state is None:
- return None
return round(self._state.white * 255)
- @property
+ @esphome_state_property
def effect(self) -> Optional[str]:
"""Return the current effect."""
- if self._state is None:
- return None
return self._state.effect
@property
diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json
new file mode 100644
index 00000000000..9d25ec6d034
--- /dev/null
+++ b/homeassistant/components/esphome/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "esphome",
+ "name": "ESPHome",
+ "documentation": "https://www.home-assistant.io/components/esphome",
+ "requirements": [
+ "aioesphomeapi==2.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@OttoWinter"
+ ]
+}
diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py
index e4fb7ef82ba..8d8fb938c68 100644
--- a/homeassistant/components/esphome/sensor.py
+++ b/homeassistant/components/esphome/sensor.py
@@ -6,14 +6,13 @@ from typing import TYPE_CHECKING, Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
-from . import EsphomeEntity, platform_async_setup_entry
+from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import ( # noqa
SensorInfo, SensorState, TextSensorInfo, TextSensorState)
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -54,11 +53,9 @@ class EsphomeSensor(EsphomeEntity):
"""Return the icon."""
return self._static_info.icon
- @property
+ @esphome_state_property
def state(self) -> Optional[str]:
"""Return the state of the entity."""
- if self._state is None:
- return None
if math.isnan(self._state.state):
return None
return '{:.{prec}f}'.format(
@@ -86,9 +83,7 @@ class EsphomeTextSensor(EsphomeEntity):
"""Return the icon."""
return self._static_info.icon
- @property
+ @esphome_state_property
def state(self) -> Optional[str]:
"""Return the state of the entity."""
- if self._state is None:
- return None
return self._state.state
diff --git a/homeassistant/components/esphome/services.yaml b/homeassistant/components/esphome/services.yaml
new file mode 100644
index 00000000000..f4c31420f9a
--- /dev/null
+++ b/homeassistant/components/esphome/services.yaml
@@ -0,0 +1 @@
+# Empty file, ESPHome services are dynamically created (user-defined services)
diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py
index e5a9d0cf446..77994d0be58 100644
--- a/homeassistant/components/esphome/switch.py
+++ b/homeassistant/components/esphome/switch.py
@@ -6,13 +6,12 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType
-from . import EsphomeEntity, platform_async_setup_entry
+from . import EsphomeEntity, platform_async_setup_entry, esphome_state_property
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import SwitchInfo, SwitchState # noqa
-DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
@@ -49,19 +48,17 @@ class EsphomeSwitch(EsphomeEntity, SwitchDevice):
@property
def assumed_state(self) -> bool:
"""Return true if we do optimistic updates."""
- return self._static_info.optimistic
+ return self._static_info.assumed_state
- @property
- def is_on(self):
+ @esphome_state_property
+ def is_on(self) -> Optional[bool]:
"""Return true if the switch is on."""
- if self._state is None:
- return None
return self._state.state
- async def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on."""
await self._client.switch_command(self._static_info.key, True)
- async def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
await self._client.switch_command(self._static_info.key, False)
diff --git a/homeassistant/components/etherscan/manifest.json b/homeassistant/components/etherscan/manifest.json
new file mode 100644
index 00000000000..452d1c4c475
--- /dev/null
+++ b/homeassistant/components/etherscan/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "etherscan",
+ "name": "Etherscan",
+ "documentation": "https://www.home-assistant.io/components/etherscan",
+ "requirements": [
+ "python-etherscan-api==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py
index 082295bfea5..83805ec4d20 100644
--- a/homeassistant/components/etherscan/sensor.py
+++ b/homeassistant/components/etherscan/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-etherscan-api==0.0.3']
-
ATTRIBUTION = "Data provided by etherscan.io"
CONF_TOKEN_ADDRESS = 'token_address'
diff --git a/homeassistant/components/eufy/__init__.py b/homeassistant/components/eufy/__init__.py
index b0bd9109363..8425780b76b 100644
--- a/homeassistant/components/eufy/__init__.py
+++ b/homeassistant/components/eufy/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['lakeside==0.12']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'eufy'
diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py
index 62bc058f155..1d08e42fff7 100644
--- a/homeassistant/components/eufy/light.py
+++ b/homeassistant/components/eufy/light.py
@@ -11,8 +11,6 @@ from homeassistant.util.color import (
color_temperature_mired_to_kelvin as mired_to_kelvin,
color_temperature_kelvin_to_mired as kelvin_to_mired)
-DEPENDENCIES = ['eufy']
-
_LOGGER = logging.getLogger(__name__)
EUFY_MAX_KELVIN = 6500
diff --git a/homeassistant/components/eufy/manifest.json b/homeassistant/components/eufy/manifest.json
new file mode 100644
index 00000000000..ec7f1fe7072
--- /dev/null
+++ b/homeassistant/components/eufy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "eufy",
+ "name": "Eufy",
+ "documentation": "https://www.home-assistant.io/components/eufy",
+ "requirements": [
+ "lakeside==0.12"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/eufy/switch.py b/homeassistant/components/eufy/switch.py
index 96d68194107..3216bfed69e 100644
--- a/homeassistant/components/eufy/switch.py
+++ b/homeassistant/components/eufy/switch.py
@@ -3,8 +3,6 @@ import logging
from homeassistant.components.switch import SwitchDevice
-DEPENDENCIES = ['eufy']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py
index 31e72c78fd6..c5fb025370d 100644
--- a/homeassistant/components/everlights/light.py
+++ b/homeassistant/components/everlights/light.py
@@ -1,9 +1,4 @@
-"""
-Support for EverLights lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.everlights/
-"""
+"""Support for EverLights lights."""
import logging
from datetime import timedelta
from typing import Tuple
@@ -20,8 +15,6 @@ import homeassistant.util.color as color_util
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.exceptions import PlatformNotReady
-REQUIREMENTS = ['pyeverlights==0.1.0']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_EVERLIGHTS = (SUPPORT_EFFECT | SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
diff --git a/homeassistant/components/everlights/manifest.json b/homeassistant/components/everlights/manifest.json
new file mode 100644
index 00000000000..9c2e1b2ae4f
--- /dev/null
+++ b/homeassistant/components/everlights/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "everlights",
+ "name": "Everlights",
+ "documentation": "https://www.home-assistant.io/components/everlights",
+ "requirements": [
+ "pyeverlights==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py
index 52bb77516e6..459a3636a06 100644
--- a/homeassistant/components/evohome/__init__.py
+++ b/homeassistant/components/evohome/__init__.py
@@ -8,21 +8,17 @@
from datetime import timedelta
import logging
-from requests.exceptions import HTTPError
+import requests.exceptions
import voluptuous as vol
from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_USERNAME, CONF_PASSWORD,
- EVENT_HOMEASSISTANT_START,
- HTTP_BAD_REQUEST, HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS
-)
+ EVENT_HOMEASSISTANT_START)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['evohomeclient==0.2.8']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'evohome'
@@ -43,6 +39,10 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
+CONF_SECRETS = [
+ CONF_USERNAME, CONF_PASSWORD,
+]
+
# These are used to help prevent E501 (line too long) violations.
GWS = 'gateways'
TCS = 'temperatureControlSystems'
@@ -66,51 +66,40 @@ def setup(hass, hass_config):
scan_interval = timedelta(
minutes=(scan_interval.total_seconds() + 59) // 60)
- from evohomeclient2 import EvohomeClient
+ import evohomeclient2
try:
- client = EvohomeClient(
+ client = evo_data['client'] = evohomeclient2.EvohomeClient(
evo_data['params'][CONF_USERNAME],
evo_data['params'][CONF_PASSWORD],
debug=False
)
- except HTTPError as err:
- if err.response.status_code == HTTP_BAD_REQUEST:
- _LOGGER.error(
- "setup(): Failed to connect with the vendor's web servers. "
- "Check your username (%s), and password are correct."
- "Unable to continue. Resolve any errors and restart HA.",
- evo_data['params'][CONF_USERNAME]
- )
-
- elif err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
- _LOGGER.error(
- "setup(): Failed to connect with the vendor's web servers. "
- "The server is not contactable. Unable to continue. "
- "Resolve any errors and restart HA."
- )
-
- elif err.response.status_code == HTTP_TOO_MANY_REQUESTS:
- _LOGGER.error(
- "setup(): Failed to connect with the vendor's web servers. "
- "You have exceeded the api rate limit. Unable to continue. "
- "Wait a while (say 10 minutes) and restart HA."
- )
-
- else:
- raise # We don't expect/handle any other HTTPErrors
-
+ except evohomeclient2.AuthenticationError as err:
+ _LOGGER.error(
+ "setup(): Failed to authenticate with the vendor's server. "
+ "Check your username and password are correct. "
+ "Resolve any errors and restart HA. Message is: %s",
+ err
+ )
return False
- finally: # Redact username, password as no longer needed
- evo_data['params'][CONF_USERNAME] = 'REDACTED'
- evo_data['params'][CONF_PASSWORD] = 'REDACTED'
+ except requests.exceptions.ConnectionError:
+ _LOGGER.error(
+ "setup(): Unable to connect with the vendor's server. "
+ "Check your network and the vendor's status page. "
+ "Resolve any errors and restart HA."
+ )
+ return False
+
+ finally: # Redact any config data that's no longer needed
+ for parameter in CONF_SECRETS:
+ evo_data['params'][parameter] = 'REDACTED' \
+ if evo_data['params'][parameter] else None
- evo_data['client'] = client
evo_data['status'] = {}
- # Redact any installation data we'll never need
+ # Redact any installation data that's no longer needed
for loc in client.installation_info:
loc['locationInfo']['locationId'] = 'REDACTED'
loc['locationInfo']['locationOwner'] = 'REDACTED'
@@ -120,18 +109,21 @@ def setup(hass, hass_config):
# Pull down the installation configuration
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
-
try:
evo_data['config'] = client.installation_info[loc_idx]
+
except IndexError:
- _LOGGER.warning(
- "setup(): Parameter '%s'=%s, is outside its range (0-%s)",
- CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1)
+ _LOGGER.error(
+ "setup(): config error, '%s' = %s, but its valid range is 0-%s. "
+ "Unable to continue. Fix any configuration errors and restart HA.",
+ CONF_LOCATION_IDX, loc_idx, len(client.installation_info) - 1
+ )
return False
if _LOGGER.isEnabledFor(logging.DEBUG):
tmp_loc = dict(evo_data['config'])
tmp_loc['locationInfo']['postcode'] = 'REDACTED'
+
if 'dhw' in tmp_loc[GWS][0][TCS][0]: # if this location has DHW...
tmp_loc[GWS][0][TCS][0]['dhw'] = '...'
@@ -139,6 +131,11 @@ def setup(hass, hass_config):
load_platform(hass, 'climate', DOMAIN, {}, hass_config)
+ if 'dhw' in evo_data['config'][GWS][0][TCS][0]:
+ _LOGGER.warning(
+ "setup(): DHW found, but this component doesn't support DHW."
+ )
+
@callback
def _first_update(event):
"""When HA has started, the hub knows to retrieve it's first update."""
diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py
index eea34e07001..cf6c21df10f 100644
--- a/homeassistant/components/evohome/climate.py
+++ b/homeassistant/components/evohome/climate.py
@@ -2,22 +2,22 @@
from datetime import datetime, timedelta
import logging
-from requests.exceptions import HTTPError
+import requests.exceptions
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_AWAY_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
- CONF_SCAN_INTERVAL, HTTP_TOO_MANY_REQUESTS, PRECISION_HALVES, STATE_OFF,
- TEMP_CELSIUS)
+ CONF_SCAN_INTERVAL, HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS,
+ PRECISION_HALVES, STATE_OFF, TEMP_CELSIUS)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from . import (
CONF_LOCATION_IDX, DATA_EVOHOME, DISPATCHER_EVOHOME, EVO_CHILD, EVO_PARENT,
- GWS, SCAN_INTERVAL_DEFAULT, TCS)
+ GWS, TCS)
_LOGGER = logging.getLogger(__name__)
@@ -81,7 +81,7 @@ async def async_setup_platform(hass, hass_config, async_add_entities,
# evohomeclient has exposed no means of accessing non-default location
# (i.e. loc_idx > 0) other than using a protected member, such as below
- tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa E501; pylint: disable=protected-access
+ tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access
_LOGGER.debug(
"Found Controller, id=%s [%s], name=%s (location_idx=%s)",
@@ -128,23 +128,43 @@ class EvoClimateDevice(ClimateDevice):
if packet['to'] & self._type and packet['signal'] == 'refresh':
self.async_schedule_update_ha_state(force_refresh=True)
- def _handle_requests_exceptions(self, err):
- if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
- # execute a backoff: pause, and also reduce rate
- old_interval = self._params[CONF_SCAN_INTERVAL]
- new_interval = min(old_interval, SCAN_INTERVAL_DEFAULT) * 2
- self._params[CONF_SCAN_INTERVAL] = new_interval
+ def _handle_exception(self, err):
+ try:
+ import evohomeclient2
+ raise err
+ except evohomeclient2.AuthenticationError:
+ _LOGGER.error(
+ "Failed to (re)authenticate with the vendor's server. "
+ "This may be a temporary error. Message is: %s",
+ err
+ )
+
+ except requests.exceptions.ConnectionError:
+ # this appears to be common with Honeywell's servers
_LOGGER.warning(
- "API rate limit has been exceeded. Suspending polling for %s "
- "seconds, and increasing '%s' from %s to %s seconds",
- new_interval * 3, CONF_SCAN_INTERVAL, old_interval,
- new_interval)
+ "Unable to connect with the vendor's server. "
+ "Check your network and the vendor's status page."
+ )
- self._timers['statusUpdated'] = datetime.now() + new_interval * 3
+ except requests.exceptions.HTTPError:
+ if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
+ _LOGGER.warning(
+ "Vendor says their server is currently unavailable. "
+ "This may be temporary; check the vendor's status page."
+ )
- else:
- raise err # we dont handle any other HTTPErrors
+ elif err.response.status_code == HTTP_TOO_MANY_REQUESTS:
+ _LOGGER.warning(
+ "The vendor's API rate limit has been exceeded. "
+ "So will cease polling, and will resume after %s seconds.",
+ (self._params[CONF_SCAN_INTERVAL] * 3).total_seconds()
+ )
+ self._timers['statusUpdated'] = datetime.now() + \
+ self._params[CONF_SCAN_INTERVAL] * 3
+
+ else:
+ raise # we don't expect/handle any other HTTPErrors
@property
def name(self) -> str:
@@ -239,7 +259,8 @@ class EvoZone(EvoClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature of the evohome Zone."""
- return self._status['temperatureStatus']['temperature']
+ return (self._status['temperatureStatus']['temperature']
+ if self._status['temperatureStatus']['isAvailable'] else None)
@property
def current_operation(self):
@@ -284,9 +305,11 @@ class EvoZone(EvoClimateDevice):
- None for PermanentOverride (i.e. indefinitely)
"""
try:
+ import evohomeclient2
self._obj.set_temperature(temperature, until)
- except HTTPError as err:
- self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member
+ except (requests.exceptions.RequestException,
+ evohomeclient2.AuthenticationError) as err:
+ self._handle_exception(err)
def set_temperature(self, **kwargs):
"""Set new target temperature, indefinitely."""
@@ -334,9 +357,11 @@ class EvoZone(EvoClimateDevice):
def _set_operation_mode(self, operation_mode):
if operation_mode == EVO_FOLLOW:
try:
- self._obj.cancel_temp_override(self._obj)
- except HTTPError as err:
- self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member
+ import evohomeclient2
+ self._obj.cancel_temp_override()
+ except (requests.exceptions.RequestException,
+ evohomeclient2.AuthenticationError) as err:
+ self._handle_exception(err)
elif operation_mode == EVO_TEMPOVER:
_LOGGER.error(
@@ -496,9 +521,11 @@ class EvoController(EvoClimateDevice):
def _set_operation_mode(self, operation_mode):
try:
+ import evohomeclient2
self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access
- except HTTPError as err:
- self._handle_requests_exceptions(err)
+ except (requests.exceptions.RequestException,
+ evohomeclient2.AuthenticationError) as err:
+ self._handle_exception(err)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode for the TCS.
@@ -532,10 +559,12 @@ class EvoController(EvoClimateDevice):
loc_idx = self._params[CONF_LOCATION_IDX]
try:
+ import evohomeclient2
self._status.update(
self._client.locations[loc_idx].status()[GWS][0][TCS][0])
- except HTTPError as err: # check if we've exceeded the api rate limit
- self._handle_requests_exceptions(err)
+ except (requests.exceptions.RequestException,
+ evohomeclient2.AuthenticationError) as err:
+ self._handle_exception(err)
else:
self._timers['statusUpdated'] = datetime.now()
self._available = True
diff --git a/homeassistant/components/evohome/manifest.json b/homeassistant/components/evohome/manifest.json
new file mode 100644
index 00000000000..b612baa476a
--- /dev/null
+++ b/homeassistant/components/evohome/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "evohome",
+ "name": "Evohome",
+ "documentation": "https://www.home-assistant.io/components/evohome",
+ "requirements": [
+ "evohomeclient==0.3.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/facebook/manifest.json b/homeassistant/components/facebook/manifest.json
new file mode 100644
index 00000000000..9632906a25a
--- /dev/null
+++ b/homeassistant/components/facebook/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "facebook",
+ "name": "Facebook",
+ "documentation": "https://www.home-assistant.io/components/facebook",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/facebook/notify.py b/homeassistant/components/facebook/notify.py
index 2c691661a29..625b922927c 100644
--- a/homeassistant/components/facebook/notify.py
+++ b/homeassistant/components/facebook/notify.py
@@ -1,9 +1,4 @@
-"""
-Facebook platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.facebook/
-"""
+"""Facebook platform for notify component."""
import json
import logging
diff --git a/homeassistant/components/facebox/image_processing.py b/homeassistant/components/facebox/image_processing.py
index 2fbd1c5c81c..2b4f184c3fd 100644
--- a/homeassistant/components/facebox/image_processing.py
+++ b/homeassistant/components/facebox/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will perform facial detection and identification via facebox.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.facebox
-"""
+"""Component for facial detection and identification via facebox."""
import base64
import logging
diff --git a/homeassistant/components/facebox/manifest.json b/homeassistant/components/facebox/manifest.json
new file mode 100644
index 00000000000..4a3eefc135c
--- /dev/null
+++ b/homeassistant/components/facebox/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "facebox",
+ "name": "Facebox",
+ "documentation": "https://www.home-assistant.io/components/facebox",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/facebox/services.yaml b/homeassistant/components/facebox/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/fail2ban/manifest.json b/homeassistant/components/fail2ban/manifest.json
new file mode 100644
index 00000000000..fc60658a3f2
--- /dev/null
+++ b/homeassistant/components/fail2ban/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "fail2ban",
+ "name": "Fail2ban",
+ "documentation": "https://www.home-assistant.io/components/fail2ban",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/familyhub/camera.py b/homeassistant/components/familyhub/camera.py
index e14bd9f1098..e9a8bcd94a6 100644
--- a/homeassistant/components/familyhub/camera.py
+++ b/homeassistant/components/familyhub/camera.py
@@ -1,9 +1,4 @@
-"""
-Family Hub camera for Samsung Refrigerators.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/camera.familyhub/
-"""
+"""Family Hub camera for Samsung Refrigerators."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['python-family-hub-local==0.0.2']
-
DEFAULT_NAME = 'FamilyHub Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/familyhub/manifest.json b/homeassistant/components/familyhub/manifest.json
new file mode 100644
index 00000000000..48a73dfb030
--- /dev/null
+++ b/homeassistant/components/familyhub/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "familyhub",
+ "name": "Familyhub",
+ "documentation": "https://www.home-assistant.io/components/familyhub",
+ "requirements": [
+ "python-family-hub-local==0.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py
index 50d6802c4d2..23015769f28 100644
--- a/homeassistant/components/fan/__init__.py
+++ b/homeassistant/components/fan/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provides functionality to interact with fans.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/fan/
-"""
+"""Provides functionality to interact with fans."""
from datetime import timedelta
import functools as ft
import logging
@@ -23,7 +18,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'fan'
-DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=30)
GROUP_NAME_ALL_FANS = 'all fans'
diff --git a/homeassistant/components/fan/manifest.json b/homeassistant/components/fan/manifest.json
new file mode 100644
index 00000000000..85bb982d2d1
--- /dev/null
+++ b/homeassistant/components/fan/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fan",
+ "name": "Fan",
+ "documentation": "https://www.home-assistant.io/components/fan",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml
index 35a81c7c934..16d3742d9ab 100644
--- a/homeassistant/components/fan/services.yaml
+++ b/homeassistant/components/fan/services.yaml
@@ -54,16 +54,6 @@ set_direction:
description: The direction to rotate. Either 'forward' or 'reverse'
example: 'forward'
-dyson_set_night_mode:
- description: Set the fan in night mode.
- fields:
- entity_id:
- description: Name(s) of the entities to enable/disable night mode
- example: 'fan.living_room'
- night_mode:
- description: Night mode status
- example: true
-
xiaomi_miio_set_buzzer_on:
description: Turn the buzzer on.
fields:
diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py
index 2e092e527c5..3fe860a81fd 100644
--- a/homeassistant/components/fastdotcom/__init__.py
+++ b/homeassistant/components/fastdotcom/__init__.py
@@ -5,14 +5,11 @@ from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
-from homeassistant.const import CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, \
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION
+from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
-REQUIREMENTS = ['fastdotcom==0.0.3']
-
DOMAIN = 'fastdotcom'
DATA_UPDATED = '{}_data_updated'.format(DOMAIN)
@@ -23,21 +20,11 @@ CONF_MANUAL = 'manual'
DEFAULT_INTERVAL = timedelta(hours=1)
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(
- vol.Schema({
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_MANUAL, default=False): cv.boolean,
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=DEFAULT_INTERVAL
- )
- )
+ DOMAIN: vol.Schema({
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
+ vol.All(cv.time_period, cv.positive_timedelta),
+ vol.Optional(CONF_MANUAL, default=False): cv.boolean,
+ })
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/fastdotcom/manifest.json b/homeassistant/components/fastdotcom/manifest.json
new file mode 100644
index 00000000000..f4bf021380c
--- /dev/null
+++ b/homeassistant/components/fastdotcom/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fastdotcom",
+ "name": "Fastdotcom",
+ "documentation": "https://www.home-assistant.io/components/fastdotcom",
+ "requirements": [
+ "fastdotcom==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py
index 37fc0815ddc..c9af8e53ab8 100644
--- a/homeassistant/components/fastdotcom/sensor.py
+++ b/homeassistant/components/fastdotcom/sensor.py
@@ -7,8 +7,6 @@ from homeassistant.helpers.restore_state import RestoreEntity
from . import DATA_UPDATED, DOMAIN as FASTDOTCOM_DOMAIN
-DEPENDENCIES = ['fastdotcom']
-
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:speedometer'
diff --git a/homeassistant/components/fedex/manifest.json b/homeassistant/components/fedex/manifest.json
new file mode 100644
index 00000000000..b34a8b8383e
--- /dev/null
+++ b/homeassistant/components/fedex/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fedex",
+ "name": "Fedex",
+ "documentation": "https://www.home-assistant.io/components/fedex",
+ "requirements": [
+ "fedexdeliverymanager==1.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fedex/sensor.py b/homeassistant/components/fedex/sensor.py
index 54c319e6441..aec1cee053c 100644
--- a/homeassistant/components/fedex/sensor.py
+++ b/homeassistant/components/fedex/sensor.py
@@ -1,27 +1,18 @@
-"""
-Sensor for Fedex packages.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fedex/
-"""
-from collections import defaultdict
+"""Sensor for Fedex packages."""
import logging
+from collections import defaultdict
from datetime import timedelta
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
- ATTR_ATTRIBUTION, CONF_UPDATE_INTERVAL,
- CONF_SCAN_INTERVAL,
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
+ ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL)
from homeassistant.helpers.entity import Entity
-from homeassistant.util import slugify
from homeassistant.util import Throttle
+from homeassistant.util import slugify
from homeassistant.util.dt import now, parse_date
-import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['fedexdeliverymanager==1.0.6']
_LOGGER = logging.getLogger(__name__)
@@ -35,21 +26,11 @@ STATUS_DELIVERED = 'delivered'
SCAN_INTERVAL = timedelta(seconds=1800)
-PLATFORM_SCHEMA = vol.All(
- PLATFORM_SCHEMA.extend({
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_NAME): cv.string,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=SCAN_INTERVAL
- )
-)
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_NAME): cv.string,
+})
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py
index 86744bfd39c..d2acb674ec7 100644
--- a/homeassistant/components/feedreader/__init__.py
+++ b/homeassistant/components/feedreader/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START, CONF_SCAN_INTERVAL
from homeassistant.helpers.event import track_time_interval
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['feedparser-homeassistant==5.2.2.dev1']
-
_LOGGER = getLogger(__name__)
CONF_URLS = 'urls'
diff --git a/homeassistant/components/feedreader/manifest.json b/homeassistant/components/feedreader/manifest.json
new file mode 100644
index 00000000000..e458d30073e
--- /dev/null
+++ b/homeassistant/components/feedreader/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "feedreader",
+ "name": "Feedreader",
+ "documentation": "https://www.home-assistant.io/components/feedreader",
+ "requirements": [
+ "feedparser-homeassistant==5.2.2.dev1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py
index 05bc1d99167..7252e617c5a 100644
--- a/homeassistant/components/ffmpeg/__init__.py
+++ b/homeassistant/components/ffmpeg/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.dispatcher import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['ha-ffmpeg==2.0']
-
DOMAIN = 'ffmpeg'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/ffmpeg/camera.py b/homeassistant/components/ffmpeg/camera.py
index 07e56cfd51f..0e8a69e0bcf 100644
--- a/homeassistant/components/ffmpeg/camera.py
+++ b/homeassistant/components/ffmpeg/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for Cameras with FFmpeg as decoder.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.ffmpeg/
-"""
+"""Support for Cameras with FFmpeg as decoder."""
import asyncio
import logging
@@ -19,8 +14,6 @@ from . import CONF_EXTRA_ARGUMENTS, CONF_INPUT, DATA_FFMPEG
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ffmpeg']
-
DEFAULT_NAME = 'FFmpeg'
DEFAULT_ARGUMENTS = "-pred 1"
diff --git a/homeassistant/components/ffmpeg/manifest.json b/homeassistant/components/ffmpeg/manifest.json
new file mode 100644
index 00000000000..4a3695e7dcc
--- /dev/null
+++ b/homeassistant/components/ffmpeg/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ffmpeg",
+ "name": "Ffmpeg",
+ "documentation": "https://www.home-assistant.io/components/ffmpeg",
+ "requirements": [
+ "ha-ffmpeg==2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py
index 8183b0e66a6..03aacf3aafb 100644
--- a/homeassistant/components/ffmpeg_motion/binary_sensor.py
+++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Provides a binary sensor which is a collection of ffmpeg tools.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
-"""
+"""Provides a binary sensor which is a collection of ffmpeg tools."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.components.ffmpeg import (
CONF_INITIAL_STATE)
from homeassistant.const import CONF_NAME
-DEPENDENCIES = ['ffmpeg']
-
_LOGGER = logging.getLogger(__name__)
CONF_RESET = 'reset'
diff --git a/homeassistant/components/ffmpeg_motion/manifest.json b/homeassistant/components/ffmpeg_motion/manifest.json
new file mode 100644
index 00000000000..e9a0e7b1014
--- /dev/null
+++ b/homeassistant/components/ffmpeg_motion/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "ffmpeg_motion",
+ "name": "Ffmpeg motion",
+ "documentation": "https://www.home-assistant.io/components/ffmpeg_motion",
+ "requirements": [],
+ "dependencies": ["ffmpeg"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ffmpeg_noise/binary_sensor.py b/homeassistant/components/ffmpeg_noise/binary_sensor.py
index 56edf1ddfd6..7fbda8ca18b 100644
--- a/homeassistant/components/ffmpeg_noise/binary_sensor.py
+++ b/homeassistant/components/ffmpeg_noise/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Provides a binary sensor which is a collection of ffmpeg tools.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
-"""
+"""Provides a binary sensor which is a collection of ffmpeg tools."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.components.ffmpeg import (
CONF_INITIAL_STATE)
from homeassistant.const import CONF_NAME
-DEPENDENCIES = ['ffmpeg']
-
_LOGGER = logging.getLogger(__name__)
CONF_PEAK = 'peak'
diff --git a/homeassistant/components/ffmpeg_noise/manifest.json b/homeassistant/components/ffmpeg_noise/manifest.json
new file mode 100644
index 00000000000..71600b31117
--- /dev/null
+++ b/homeassistant/components/ffmpeg_noise/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "ffmpeg_noise",
+ "name": "Ffmpeg noise",
+ "documentation": "https://www.home-assistant.io/components/ffmpeg_noise",
+ "requirements": [],
+ "dependencies": ["ffmpeg"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py
index 9a6ccccb5e3..f78afbf10e5 100644
--- a/homeassistant/components/fibaro/__init__.py
+++ b/homeassistant/components/fibaro/__init__.py
@@ -13,8 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import convert, slugify
-REQUIREMENTS = ['fiblary3==0.1.7']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_ENERGY_KWH = 'current_energy_kwh'
@@ -26,20 +24,11 @@ CONF_DIMMING = 'dimming'
CONF_GATEWAYS = 'gateways'
CONF_PLUGINS = 'plugins'
CONF_RESET_COLOR = 'reset_color'
-
DOMAIN = 'fibaro'
-
FIBARO_CONTROLLERS = 'fibaro_controllers'
FIBARO_DEVICES = 'fibaro_devices'
-
-FIBARO_COMPONENTS = [
- 'binary_sensor',
- 'cover',
- 'light',
- 'scene',
- 'sensor',
- 'switch',
-]
+FIBARO_COMPONENTS = ['binary_sensor', 'climate', 'cover', 'light',
+ 'scene', 'sensor', 'switch']
FIBARO_TYPEMAP = {
'com.fibaro.multilevelSensor': "sensor",
@@ -56,7 +45,11 @@ FIBARO_TYPEMAP = {
'com.fibaro.remoteSwitch': 'switch',
'com.fibaro.sensor': 'sensor',
'com.fibaro.colorController': 'light',
- 'com.fibaro.securitySensor': 'binary_sensor'
+ 'com.fibaro.securitySensor': 'binary_sensor',
+ 'com.fibaro.hvac': 'climate',
+ 'com.fibaro.setpoint': 'climate',
+ 'com.fibaro.FGT001': 'climate',
+ 'com.fibaro.thermostatDanfoss': 'climate'
}
DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({
@@ -174,6 +167,16 @@ class FibaroController():
"""Register device with a callback for updates."""
self._callbacks[device_id] = callback
+ def get_children(self, device_id):
+ """Get a list of child devices."""
+ return [
+ device for device in self._device_map.values()
+ if device.parentId == device_id]
+
+ def get_siblings(self, device_id):
+ """Get the siblings of a device."""
+ return self.get_children(self._device_map[device_id].parentId)
+
@staticmethod
def _map_device_to_type(device):
"""Map device to HA device type."""
@@ -217,9 +220,9 @@ class FibaroController():
room_name = self._room_map[device.roomID].name
device.room_name = room_name
device.friendly_name = '{} {}'.format(room_name, device.name)
- device.ha_id = '{}_{}_{}'.format(
+ device.ha_id = 'scene_{}_{}_{}'.format(
slugify(room_name), slugify(device.name), device.id)
- device.unique_id_str = "{}.{}".format(
+ device.unique_id_str = "{}.scene.{}".format(
self.hub_serial, device.id)
self._scene_map[device.id] = device
self.fibaro_devices['scene'].append(device)
@@ -229,6 +232,7 @@ class FibaroController():
devices = self._client.devices.list()
self._device_map = {}
self.fibaro_devices = defaultdict(list)
+ last_climate_parent = None
for device in devices:
try:
device.fibaro_controller = self
@@ -249,15 +253,26 @@ class FibaroController():
self._device_config.get(device.ha_id, {})
else:
device.mapped_type = None
- if device.mapped_type:
+ dtype = device.mapped_type
+ if dtype:
device.unique_id_str = "{}.{}".format(
self.hub_serial, device.id)
self._device_map[device.id] = device
- self.fibaro_devices[device.mapped_type].append(device)
- _LOGGER.debug("%s (%s, %s) -> %s. Prop: %s Actions: %s",
+ if dtype != 'climate':
+ self.fibaro_devices[dtype].append(device)
+ else:
+ # if a sibling of this has been added, skip this one
+ # otherwise add the first visible device in the group
+ # which is a hack, but solves a problem with FGT having
+ # hidden compatibility devices before the real device
+ if last_climate_parent != device.parentId and \
+ device.visible:
+ self.fibaro_devices[dtype].append(device)
+ last_climate_parent = device.parentId
+ _LOGGER.debug("%s (%s, %s) -> %s %s",
device.ha_id, device.type,
- device.baseType, device.mapped_type,
- str(device.properties), str(device.actions))
+ device.baseType, dtype,
+ str(device))
except (KeyError, ValueError):
pass
diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py
index f71a5f3662e..44448227a1c 100644
--- a/homeassistant/components/fibaro/binary_sensor.py
+++ b/homeassistant/components/fibaro/binary_sensor.py
@@ -7,8 +7,6 @@ from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON
from . import FIBARO_DEVICES, FibaroDevice
-DEPENDENCIES = ['fibaro']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py
new file mode 100644
index 00000000000..4b12a907ce3
--- /dev/null
+++ b/homeassistant/components/fibaro/climate.py
@@ -0,0 +1,289 @@
+"""Support for Fibaro thermostats."""
+import logging
+
+from homeassistant.components.climate.const import (
+ STATE_AUTO, STATE_COOL, STATE_DRY,
+ STATE_ECO, STATE_FAN_ONLY, STATE_HEAT,
+ STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE,
+ SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE)
+
+from homeassistant.components.climate import (
+ ClimateDevice)
+
+from homeassistant.const import (
+ ATTR_TEMPERATURE,
+ STATE_OFF,
+ TEMP_CELSIUS,
+ TEMP_FAHRENHEIT)
+
+from . import (
+ FIBARO_DEVICES, FibaroDevice)
+
+SPEED_LOW = 'low'
+SPEED_MEDIUM = 'medium'
+SPEED_HIGH = 'high'
+
+# State definitions missing from HA, but defined by Z-Wave standard.
+# We map them to states known supported by HA here:
+STATE_AUXILIARY = STATE_HEAT
+STATE_RESUME = STATE_HEAT
+STATE_MOIST = STATE_DRY
+STATE_AUTO_CHANGEOVER = STATE_AUTO
+STATE_ENERGY_HEAT = STATE_ECO
+STATE_ENERGY_COOL = STATE_COOL
+STATE_FULL_POWER = STATE_AUTO
+STATE_FORCE_OPEN = STATE_MANUAL
+STATE_AWAY = STATE_AUTO
+STATE_FURNACE = STATE_HEAT
+
+FAN_AUTO_HIGH = 'auto_high'
+FAN_AUTO_MEDIUM = 'auto_medium'
+FAN_CIRCULATION = 'circulation'
+FAN_HUMIDITY_CIRCULATION = 'humidity_circulation'
+FAN_LEFT_RIGHT = 'left_right'
+FAN_UP_DOWN = 'up_down'
+FAN_QUIET = 'quiet'
+
+_LOGGER = logging.getLogger(__name__)
+
+# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
+# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding
+FANMODES = {
+ 0: STATE_OFF,
+ 1: SPEED_LOW,
+ 2: FAN_AUTO_HIGH,
+ 3: SPEED_HIGH,
+ 4: FAN_AUTO_MEDIUM,
+ 5: SPEED_MEDIUM,
+ 6: FAN_CIRCULATION,
+ 7: FAN_HUMIDITY_CIRCULATION,
+ 8: FAN_LEFT_RIGHT,
+ 9: FAN_UP_DOWN,
+ 10: FAN_QUIET,
+ 128: STATE_AUTO
+}
+
+# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04
+# Table 130, Thermostat Mode Set version 3::Mode encoding.
+OPMODES = {
+ 0: STATE_OFF,
+ 1: STATE_HEAT,
+ 2: STATE_COOL,
+ 3: STATE_AUTO,
+ 4: STATE_AUXILIARY,
+ 5: STATE_RESUME,
+ 6: STATE_FAN_ONLY,
+ 7: STATE_FURNACE,
+ 8: STATE_DRY,
+ 9: STATE_MOIST,
+ 10: STATE_AUTO_CHANGEOVER,
+ 11: STATE_ENERGY_HEAT,
+ 12: STATE_ENERGY_COOL,
+ 13: STATE_AWAY,
+ 15: STATE_FULL_POWER,
+ 31: STATE_FORCE_OPEN
+}
+
+SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Perform the setup for Fibaro controller devices."""
+ if discovery_info is None:
+ return
+
+ add_entities(
+ [FibaroThermostat(device)
+ for device in hass.data[FIBARO_DEVICES]['climate']], True)
+
+
+class FibaroThermostat(FibaroDevice, ClimateDevice):
+ """Representation of a Fibaro Thermostat."""
+
+ def __init__(self, fibaro_device):
+ """Initialize the Fibaro device."""
+ super().__init__(fibaro_device)
+ self._temp_sensor_device = None
+ self._target_temp_device = None
+ self._op_mode_device = None
+ self._fan_mode_device = None
+ self._support_flags = 0
+ self.entity_id = 'climate.{}'.format(self.ha_id)
+ self._fan_mode_to_state = {}
+ self._fan_state_to_mode = {}
+ self._op_mode_to_state = {}
+ self._op_state_to_mode = {}
+
+ siblings = fibaro_device.fibaro_controller.get_siblings(
+ fibaro_device.id)
+ tempunit = 'C'
+ for device in siblings:
+ if device.type == 'com.fibaro.temperatureSensor':
+ self._temp_sensor_device = FibaroDevice(device)
+ tempunit = device.properties.unit
+ if 'setTargetLevel' in device.actions or \
+ 'setThermostatSetpoint' in device.actions:
+ self._target_temp_device = FibaroDevice(device)
+ self._support_flags |= SUPPORT_TARGET_TEMPERATURE
+ tempunit = device.properties.unit
+ if 'setMode' in device.actions or \
+ 'setOperatingMode' in device.actions:
+ self._op_mode_device = FibaroDevice(device)
+ self._support_flags |= SUPPORT_OPERATION_MODE
+ if 'setFanMode' in device.actions:
+ self._fan_mode_device = FibaroDevice(device)
+ self._support_flags |= SUPPORT_FAN_MODE
+
+ if tempunit == 'F':
+ self._unit_of_temp = TEMP_FAHRENHEIT
+ else:
+ self._unit_of_temp = TEMP_CELSIUS
+
+ if self._fan_mode_device:
+ fan_modes = self._fan_mode_device.fibaro_device.\
+ properties.supportedModes.split(",")
+ for mode in fan_modes:
+ try:
+ self._fan_mode_to_state[int(mode)] = FANMODES[int(mode)]
+ self._fan_state_to_mode[FANMODES[int(mode)]] = int(mode)
+ except KeyError:
+ self._fan_mode_to_state[int(mode)] = 'unknown'
+
+ if self._op_mode_device:
+ prop = self._op_mode_device.fibaro_device.properties
+ if "supportedOperatingModes" in prop:
+ op_modes = prop.supportedOperatingModes.split(",")
+ elif "supportedModes" in prop:
+ op_modes = prop.supportedModes.split(",")
+ for mode in op_modes:
+ try:
+ self._op_mode_to_state[int(mode)] = OPMODES[int(mode)]
+ self._op_state_to_mode[OPMODES[int(mode)]] = int(mode)
+ except KeyError:
+ self._op_mode_to_state[int(mode)] = 'unknown'
+
+ async def async_added_to_hass(self):
+ """Call when entity is added to hass."""
+ _LOGGER.debug("Climate %s\n"
+ "- _temp_sensor_device %s\n"
+ "- _target_temp_device %s\n"
+ "- _op_mode_device %s\n"
+ "- _fan_mode_device %s",
+ self.ha_id,
+ self._temp_sensor_device.ha_id
+ if self._temp_sensor_device else "None",
+ self._target_temp_device.ha_id
+ if self._target_temp_device else "None",
+ self._op_mode_device.ha_id
+ if self._op_mode_device else "None",
+ self._fan_mode_device.ha_id
+ if self._fan_mode_device else "None")
+ await super().async_added_to_hass()
+
+ # Register update callback for child devices
+ siblings = self.fibaro_device.fibaro_controller.get_siblings(
+ self.fibaro_device.id)
+ for device in siblings:
+ if device != self.fibaro_device:
+ self.controller.register(device.id,
+ self._update_callback)
+
+ @property
+ def supported_features(self):
+ """Return the list of supported features."""
+ return self._support_flags
+
+ @property
+ def fan_list(self):
+ """Return the list of available fan modes."""
+ if self._fan_mode_device is None:
+ return None
+ return list(self._fan_state_to_mode)
+
+ @property
+ def current_fan_mode(self):
+ """Return the fan setting."""
+ if self._fan_mode_device is None:
+ return None
+
+ mode = int(self._fan_mode_device.fibaro_device.properties.mode)
+ return self._fan_mode_to_state[mode]
+
+ def set_fan_mode(self, fan_mode):
+ """Set new target fan mode."""
+ if self._fan_mode_device is None:
+ return
+ self._fan_mode_device.action(
+ "setFanMode", self._fan_state_to_mode[fan_mode])
+
+ @property
+ def current_operation(self):
+ """Return current operation ie. heat, cool, idle."""
+ if self._op_mode_device is None:
+ return None
+
+ if "operatingMode" in self._op_mode_device.fibaro_device.properties:
+ mode = int(self._op_mode_device.fibaro_device.
+ properties.operatingMode)
+ else:
+ mode = int(self._op_mode_device.fibaro_device.properties.mode)
+ return self._op_mode_to_state.get(mode)
+
+ @property
+ def operation_list(self):
+ """Return the list of available operation modes."""
+ if self._op_mode_device is None:
+ return None
+ return list(self._op_state_to_mode)
+
+ def set_operation_mode(self, operation_mode):
+ """Set new target operation mode."""
+ if self._op_mode_device is None:
+ return
+ if "setOperatingMode" in self._op_mode_device.fibaro_device.actions:
+ self._op_mode_device.action(
+ "setOperatingMode", self._op_state_to_mode[operation_mode])
+ elif "setMode" in self._op_mode_device.fibaro_device.actions:
+ self._op_mode_device.action(
+ "setMode", self._op_state_to_mode[operation_mode])
+
+ @property
+ def temperature_unit(self):
+ """Return the unit of measurement."""
+ return self._unit_of_temp
+
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ if self._temp_sensor_device:
+ device = self._temp_sensor_device.fibaro_device
+ return float(device.properties.value)
+ return None
+
+ @property
+ def target_temperature(self):
+ """Return the temperature we try to reach."""
+ if self._target_temp_device:
+ device = self._target_temp_device.fibaro_device
+ return float(device.properties.targetLevel)
+ return None
+
+ def set_temperature(self, **kwargs):
+ """Set new target temperatures."""
+ temperature = kwargs.get(ATTR_TEMPERATURE)
+ target = self._target_temp_device
+ if temperature is not None:
+ if "setThermostatSetpoint" in target.fibaro_device.actions:
+ target.action("setThermostatSetpoint",
+ self._op_state_to_mode[self.current_operation],
+ temperature)
+ else:
+ target.action("setTargetLevel",
+ temperature)
+
+ @property
+ def is_on(self):
+ """Return true if on."""
+ if self.current_operation == STATE_OFF:
+ return False
+ return True
diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py
index 0f5cc32bc96..0ccbed0144b 100644
--- a/homeassistant/components/fibaro/cover.py
+++ b/homeassistant/components/fibaro/cover.py
@@ -6,8 +6,6 @@ from homeassistant.components.cover import (
from . import FIBARO_DEVICES, FibaroDevice
-DEPENDENCIES = ['fibaro']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py
index 600b566b36b..a741de972f0 100644
--- a/homeassistant/components/fibaro/light.py
+++ b/homeassistant/components/fibaro/light.py
@@ -14,8 +14,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['fibaro']
-
def scaleto255(value):
"""Scale the input value from 0-100 to 0-255."""
diff --git a/homeassistant/components/fibaro/manifest.json b/homeassistant/components/fibaro/manifest.json
new file mode 100644
index 00000000000..3574e6254de
--- /dev/null
+++ b/homeassistant/components/fibaro/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fibaro",
+ "name": "Fibaro",
+ "documentation": "https://www.home-assistant.io/components/fibaro",
+ "requirements": [
+ "fiblary3==0.1.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py
index 93f0cd5b63a..f9f96844319 100644
--- a/homeassistant/components/fibaro/scene.py
+++ b/homeassistant/components/fibaro/scene.py
@@ -5,8 +5,6 @@ from homeassistant.components.scene import Scene
from . import FIBARO_DEVICES, FibaroDevice
-DEPENDENCIES = ['fibaro']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py
index 20a37fd3c23..db9d103d87e 100644
--- a/homeassistant/components/fibaro/sensor.py
+++ b/homeassistant/components/fibaro/sensor.py
@@ -22,7 +22,6 @@ SENSOR_TYPES = {
['Light', 'lx', None, DEVICE_CLASS_ILLUMINANCE]
}
-DEPENDENCIES = ['fibaro']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py
index 024531f62c7..f134b424484 100644
--- a/homeassistant/components/fibaro/switch.py
+++ b/homeassistant/components/fibaro/switch.py
@@ -6,7 +6,6 @@ from homeassistant.util import convert
from . import FIBARO_DEVICES, FibaroDevice
-DEPENDENCIES = ['fibaro']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/fido/manifest.json b/homeassistant/components/fido/manifest.json
new file mode 100644
index 00000000000..343a21ff072
--- /dev/null
+++ b/homeassistant/components/fido/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fido",
+ "name": "Fido",
+ "documentation": "https://www.home-assistant.io/components/fido",
+ "requirements": [
+ "pyfido==2.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py
index 00754c5ba68..ea66acaf808 100644
--- a/homeassistant/components/fido/sensor.py
+++ b/homeassistant/components/fido/sensor.py
@@ -20,8 +20,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyfido==2.1.1']
-
_LOGGER = logging.getLogger(__name__)
KILOBITS = 'Kb' # type: str
diff --git a/homeassistant/components/file/manifest.json b/homeassistant/components/file/manifest.json
new file mode 100644
index 00000000000..581b0e14156
--- /dev/null
+++ b/homeassistant/components/file/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "file",
+ "name": "File",
+ "documentation": "https://www.home-assistant.io/components/file",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/file/notify.py b/homeassistant/components/file/notify.py
index d449476469b..07718dcf36c 100644
--- a/homeassistant/components/file/notify.py
+++ b/homeassistant/components/file/notify.py
@@ -1,9 +1,4 @@
-"""
-Support for file notification.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.file/
-"""
+"""Support for file notification."""
import logging
import os
diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py
index 3e2a5c21be8..a618c1e56dc 100644
--- a/homeassistant/components/file/sensor.py
+++ b/homeassistant/components/file/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for sensor value(s) stored in local files.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.file/
-"""
+"""Support for sensor value(s) stored in local files."""
import os
import logging
diff --git a/homeassistant/components/filesize/manifest.json b/homeassistant/components/filesize/manifest.json
new file mode 100644
index 00000000000..f76bcd27466
--- /dev/null
+++ b/homeassistant/components/filesize/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "filesize",
+ "name": "Filesize",
+ "documentation": "https://www.home-assistant.io/components/filesize",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py
index 4df858fda23..3e1394c72d6 100644
--- a/homeassistant/components/filesize/sensor.py
+++ b/homeassistant/components/filesize/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for monitoring the size of a file.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.filesize/
-"""
+"""Sensor for monitoring the size of a file."""
import datetime
import logging
import os
diff --git a/homeassistant/components/filter/manifest.json b/homeassistant/components/filter/manifest.json
new file mode 100644
index 00000000000..28f061d26f7
--- /dev/null
+++ b/homeassistant/components/filter/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "filter",
+ "name": "Filter",
+ "documentation": "https://www.home-assistant.io/components/filter",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py
index 92e2cc751ac..734caa31270 100644
--- a/homeassistant/components/filter/sensor.py
+++ b/homeassistant/components/filter/sensor.py
@@ -1,9 +1,4 @@
-"""
-Allows the creation of a sensor that filters state property.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.filter/
-"""
+"""Allows the creation of a sensor that filters state property."""
import logging
import statistics
from collections import deque, Counter
diff --git a/homeassistant/components/fints/manifest.json b/homeassistant/components/fints/manifest.json
new file mode 100644
index 00000000000..e3580676290
--- /dev/null
+++ b/homeassistant/components/fints/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fints",
+ "name": "Fints",
+ "documentation": "https://www.home-assistant.io/components/fints",
+ "requirements": [
+ "fints==1.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py
index e5dae70070b..cb993ada8da 100644
--- a/homeassistant/components/fints/sensor.py
+++ b/homeassistant/components/fints/sensor.py
@@ -1,9 +1,4 @@
-"""
-Read the balance of your bank accounts via FinTS.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fints/
-"""
+"""Read the balance of your bank accounts via FinTS."""
from collections import namedtuple
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.const import CONF_USERNAME, CONF_PIN, CONF_URL, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['fints==1.0.1']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(hours=4)
diff --git a/homeassistant/components/fitbit/manifest.json b/homeassistant/components/fitbit/manifest.json
new file mode 100644
index 00000000000..baf0d8aaed1
--- /dev/null
+++ b/homeassistant/components/fitbit/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "fitbit",
+ "name": "Fitbit",
+ "documentation": "https://www.home-assistant.io/components/fitbit",
+ "requirements": [
+ "fitbit==0.3.0"
+ ],
+ "dependencies": [
+ "configurator",
+ "http"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py
index d5d9150e4e8..889920239ed 100644
--- a/homeassistant/components/fitbit/sensor.py
+++ b/homeassistant/components/fitbit/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Fitbit API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fitbit/
-"""
+"""Support for the Fitbit API."""
import os
import logging
import datetime
@@ -22,8 +17,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['fitbit==0.3.0']
-
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@@ -37,8 +30,6 @@ CONF_MONITORED_RESOURCES = 'monitored_resources'
CONF_CLOCK_FORMAT = 'clock_format'
ATTRIBUTION = 'Data provided by Fitbit.com'
-DEPENDENCIES = ['http']
-
FITBIT_AUTH_CALLBACK_PATH = '/api/fitbit/callback'
FITBIT_AUTH_START = '/api/fitbit'
FITBIT_CONFIG_FILE = 'fitbit.conf'
diff --git a/homeassistant/components/fixer/manifest.json b/homeassistant/components/fixer/manifest.json
new file mode 100644
index 00000000000..1e010bb06ed
--- /dev/null
+++ b/homeassistant/components/fixer/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "fixer",
+ "name": "Fixer",
+ "documentation": "https://www.home-assistant.io/components/fixer",
+ "requirements": [
+ "fixerio==1.0.0a0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py
index c46fa751319..4cf2b0b9243 100644
--- a/homeassistant/components/fixer/sensor.py
+++ b/homeassistant/components/fixer/sensor.py
@@ -1,9 +1,4 @@
-"""
-Currency exchange rate support that comes from fixer.io.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fixer/
-"""
+"""Currency exchange rate support that comes from fixer.io."""
from datetime import timedelta
import logging
@@ -14,8 +9,6 @@ from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['fixerio==1.0.0a0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_EXCHANGE_RATE = 'Exchange rate'
diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py
index fe7b5ff8e7c..d1cf97f047a 100644
--- a/homeassistant/components/flexit/climate.py
+++ b/homeassistant/components/flexit/climate.py
@@ -25,9 +25,6 @@ from homeassistant.components.modbus import (
CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyflexit==0.3']
-DEPENDENCIES = ['modbus']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)),
diff --git a/homeassistant/components/flexit/manifest.json b/homeassistant/components/flexit/manifest.json
new file mode 100644
index 00000000000..0ee0e81143c
--- /dev/null
+++ b/homeassistant/components/flexit/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "flexit",
+ "name": "Flexit",
+ "documentation": "https://www.home-assistant.io/components/flexit",
+ "requirements": [
+ "pyflexit==0.3"
+ ],
+ "dependencies": [
+ "modbus"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py
index baf1d469b28..3381550b578 100644
--- a/homeassistant/components/flic/binary_sensor.py
+++ b/homeassistant/components/flic/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support to use flic buttons as a binary sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.flic/
-"""
+"""Support to use flic buttons as a binary sensor."""
import logging
import threading
@@ -16,8 +11,6 @@ from homeassistant.const import (
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
-REQUIREMENTS = ['pyflic-homeassistant==0.4.dev0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_TIMEOUT = 3
diff --git a/homeassistant/components/flic/manifest.json b/homeassistant/components/flic/manifest.json
new file mode 100644
index 00000000000..827bcb167c3
--- /dev/null
+++ b/homeassistant/components/flic/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "flic",
+ "name": "Flic",
+ "documentation": "https://www.home-assistant.io/components/flic",
+ "requirements": [
+ "pyflic-homeassistant==0.4.dev0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/flock/manifest.json b/homeassistant/components/flock/manifest.json
new file mode 100644
index 00000000000..a5af541eeee
--- /dev/null
+++ b/homeassistant/components/flock/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "flock",
+ "name": "Flock",
+ "documentation": "https://www.home-assistant.io/components/flock",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/flock/notify.py b/homeassistant/components/flock/notify.py
index b37483d2f13..384bf26599a 100644
--- a/homeassistant/components/flock/notify.py
+++ b/homeassistant/components/flock/notify.py
@@ -1,9 +1,4 @@
-"""
-Flock platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.flock/
-"""
+"""Flock platform for notify component."""
import asyncio
import logging
diff --git a/homeassistant/components/flunearyou/manifest.json b/homeassistant/components/flunearyou/manifest.json
new file mode 100644
index 00000000000..76053f75081
--- /dev/null
+++ b/homeassistant/components/flunearyou/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "flunearyou",
+ "name": "Flunearyou",
+ "documentation": "https://www.home-assistant.io/components/flunearyou",
+ "requirements": [
+ "pyflunearyou==1.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py
index 8dfb330cf5c..148a3ee4159 100644
--- a/homeassistant/components/flunearyou/sensor.py
+++ b/homeassistant/components/flunearyou/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for user- and CDC-based flu info sensors from Flu Near You.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.flunearyou/
-"""
+"""Support for user- and CDC-based flu info sensors from Flu Near You."""
import logging
from datetime import timedelta
@@ -18,7 +13,6 @@ from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyflunearyou==1.0.3']
_LOGGER = logging.getLogger(__name__)
ATTR_CITY = 'city'
diff --git a/homeassistant/components/flux/manifest.json b/homeassistant/components/flux/manifest.json
new file mode 100644
index 00000000000..d4d67edbd35
--- /dev/null
+++ b/homeassistant/components/flux/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "flux",
+ "name": "Flux",
+ "documentation": "https://www.home-assistant.io/components/flux",
+ "requirements": [],
+ "dependencies": [
+ "light"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/flux/services.yaml b/homeassistant/components/flux/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py
index fdd0c09b9d7..f0134f04d89 100644
--- a/homeassistant/components/flux/switch.py
+++ b/homeassistant/components/flux/switch.py
@@ -42,8 +42,6 @@ MODE_XY = 'xy'
MODE_MIRED = 'mired'
MODE_RGB = 'rgb'
DEFAULT_MODE = MODE_XY
-DEPENDENCIES = ['light']
-
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'flux',
diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py
index 17c288da6c2..38809e94c92 100644
--- a/homeassistant/components/flux_led/light.py
+++ b/homeassistant/components/flux_led/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Flux lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.flux_led/
-"""
+"""Support for Flux lights."""
import logging
import socket
import random
@@ -20,8 +15,6 @@ from homeassistant.components.light import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
-REQUIREMENTS = ['flux_led==0.22']
-
_LOGGER = logging.getLogger(__name__)
CONF_AUTOMATIC_ADD = 'automatic_add'
@@ -151,7 +144,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if ipaddr in light_ips:
continue
device['name'] = '{} {}'.format(device['id'], ipaddr)
- device[ATTR_MODE] = MODE_RGBW
+ device[ATTR_MODE] = None
device[CONF_PROTOCOL] = None
device[CONF_CUSTOM_EFFECT] = None
light = FluxLight(device)
@@ -258,16 +251,15 @@ class FluxLight(Light):
for effect, code in EFFECT_MAP.items():
if current_mode == code:
return effect
-
return None
async def async_turn_on(self, **kwargs):
"""Turn the specified or all lights on and wait for state."""
await self.hass.async_add_executor_job(partial(self._turn_on,
**kwargs))
- # The bulb needs a second to tell its new values,
- # so we wait 2 seconds before updating
- await sleep(2)
+ # The bulb needs a bit to tell its new values,
+ # so we wait 1 second before updating
+ await sleep(1)
def _turn_on(self, **kwargs):
"""Turn the specified or all lights on."""
@@ -278,55 +270,47 @@ class FluxLight(Light):
effect = kwargs.get(ATTR_EFFECT)
white = kwargs.get(ATTR_WHITE_VALUE)
- # Show warning if effect set with rgb, brightness, or white level
- if effect and (brightness or white or hs_color):
- _LOGGER.warning("RGB, brightness and white level are ignored when"
- " an effect is specified for a flux bulb")
-
- # Random color effect
- if effect == EFFECT_RANDOM:
- self._bulb.setRgb(random.randint(0, 255),
- random.randint(0, 255),
- random.randint(0, 255))
+ if all(item is None for item in [hs_color, brightness, effect, white]):
return
- if effect == EFFECT_CUSTOM:
- if self._custom_effect:
- self._bulb.setCustomPattern(
- self._custom_effect[CONF_COLORS],
- self._custom_effect[CONF_SPEED_PCT],
- self._custom_effect[CONF_TRANSITION])
- return
-
- # Effect selection
- if effect in EFFECT_MAP:
- self._bulb.setPresetPattern(EFFECT_MAP[effect], 50)
- return
-
- # Preserve current brightness on color/white level change
- if brightness is None:
- brightness = self.brightness
-
- if hs_color:
- self._color = (hs_color[0], hs_color[1], brightness / 255 * 100)
- elif brightness and (hs_color is None) and self._mode != MODE_WHITE:
- self._color = (self._color[0], self._color[1],
- brightness / 255 * 100)
-
# handle W only mode (use brightness instead of white value)
if self._mode == MODE_WHITE:
- self._bulb.setRgbw(0, 0, 0, w=brightness)
-
+ if brightness is not None:
+ self._bulb.setWarmWhite255(brightness)
+ return
+ if effect is not None:
+ # Random color effect
+ if effect == EFFECT_RANDOM:
+ self._bulb.setRgb(random.randint(0, 255),
+ random.randint(0, 255),
+ random.randint(0, 255))
+ elif effect == EFFECT_CUSTOM:
+ if self._custom_effect:
+ self._bulb.setCustomPattern(
+ self._custom_effect[CONF_COLORS],
+ self._custom_effect[CONF_SPEED_PCT],
+ self._custom_effect[CONF_TRANSITION])
+ # Effect selection
+ elif effect in EFFECT_MAP:
+ self._bulb.setPresetPattern(EFFECT_MAP[effect], 50)
+ return
+ # Preserve current brightness on color/white level change
+ if hs_color is not None:
+ if brightness is None:
+ brightness = self.brightness
+ color = (hs_color[0], hs_color[1], brightness / 255 * 100)
+ elif brightness is not None:
+ color = (self._color[0], self._color[1],
+ brightness / 255 * 100)
# handle RGBW mode
- elif self._mode == MODE_RGBW:
+ if self._mode == MODE_RGBW:
if white is None:
- self._bulb.setRgbw(*color_util.color_hsv_to_RGB(*self._color))
+ self._bulb.setRgbw(*color_util.color_hsv_to_RGB(*color))
else:
self._bulb.setRgbw(w=white)
# handle RGB mode
else:
- self._bulb.setRgb(*color_util.color_hsv_to_RGB(*self._color))
- return
+ self._bulb.setRgb(*color_util.color_hsv_to_RGB(*color))
def turn_off(self, **kwargs):
"""Turn the specified or all lights off."""
@@ -338,10 +322,6 @@ class FluxLight(Light):
try:
self._connect()
self._error_reported = False
- if self._bulb.getRgb() != (0, 0, 0):
- color = self._bulb.getRgbw()
- self._color = color_util.color_RGB_to_hsv(*color[0:3])
- self._white_value = color[3]
except socket.error:
self._disconnect()
if not self._error_reported:
@@ -350,7 +330,9 @@ class FluxLight(Light):
self._error_reported = True
return
self._bulb.update_state(retry=2)
- if self._bulb.getRgb() != (0, 0, 0):
+ if self._mode != MODE_WHITE and self._bulb.getRgb() != (0, 0, 0):
color = self._bulb.getRgbw()
self._color = color_util.color_RGB_to_hsv(*color[0:3])
self._white_value = color[3]
+ elif self._mode == MODE_WHITE:
+ self._white_value = self._bulb.getRgbw()[3]
diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json
new file mode 100644
index 00000000000..0d00275200c
--- /dev/null
+++ b/homeassistant/components/flux_led/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "flux_led",
+ "name": "Flux led",
+ "documentation": "https://www.home-assistant.io/components/flux_led",
+ "requirements": [
+ "flux_led==0.22"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/folder/manifest.json b/homeassistant/components/folder/manifest.json
new file mode 100644
index 00000000000..7a0bf76e0aa
--- /dev/null
+++ b/homeassistant/components/folder/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "folder",
+ "name": "Folder",
+ "documentation": "https://www.home-assistant.io/components/folder",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py
index 8101bbd059a..d742166a192 100644
--- a/homeassistant/components/folder/sensor.py
+++ b/homeassistant/components/folder/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for monitoring the contents of a folder.
-
-For more details about this platform, refer to the documentation at
-https://home-assistant.io/components/sensor.folder/
-"""
+"""Sensor for monitoring the contents of a folder."""
from datetime import timedelta
import glob
import logging
diff --git a/homeassistant/components/folder_watcher/__init__.py b/homeassistant/components/folder_watcher/__init__.py
index babfbd9e9aa..411f6b480dc 100644
--- a/homeassistant/components/folder_watcher/__init__.py
+++ b/homeassistant/components/folder_watcher/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['watchdog==0.8.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_FOLDER = 'folder'
diff --git a/homeassistant/components/folder_watcher/manifest.json b/homeassistant/components/folder_watcher/manifest.json
new file mode 100644
index 00000000000..1a5b547e5ff
--- /dev/null
+++ b/homeassistant/components/folder_watcher/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "folder_watcher",
+ "name": "Folder watcher",
+ "documentation": "https://www.home-assistant.io/components/folder_watcher",
+ "requirements": [
+ "watchdog==0.8.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/foobot/manifest.json b/homeassistant/components/foobot/manifest.json
new file mode 100644
index 00000000000..9ed95597e41
--- /dev/null
+++ b/homeassistant/components/foobot/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "foobot",
+ "name": "Foobot",
+ "documentation": "https://www.home-assistant.io/components/foobot",
+ "requirements": [
+ "foobot_async==0.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/foobot/sensor.py b/homeassistant/components/foobot/sensor.py
index 62139c53c4b..f59392bde98 100644
--- a/homeassistant/components/foobot/sensor.py
+++ b/homeassistant/components/foobot/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Foobot indoor air quality monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.foobot/
-"""
+"""Support for the Foobot indoor air quality monitor."""
import asyncio
import logging
from datetime import timedelta
@@ -21,8 +16,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['foobot_async==0.3.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_HUMIDITY = 'humidity'
diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py
index 8adac658625..f83c3f1966a 100644
--- a/homeassistant/components/foscam/camera.py
+++ b/homeassistant/components/foscam/camera.py
@@ -1,9 +1,4 @@
-"""
-This component provides basic support for Foscam IP cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.foscam/
-"""
+"""This component provides basic support for Foscam IP cameras."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['libpyfoscam==1.0']
-
CONF_IP = 'ip'
CONF_RTSP_PORT = 'rtsp_port'
diff --git a/homeassistant/components/foscam/manifest.json b/homeassistant/components/foscam/manifest.json
new file mode 100644
index 00000000000..b05aa956b42
--- /dev/null
+++ b/homeassistant/components/foscam/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "foscam",
+ "name": "Foscam",
+ "documentation": "https://www.home-assistant.io/components/foscam",
+ "requirements": [
+ "libpyfoscam==1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py
index 0c5a48049ec..dd834999888 100644
--- a/homeassistant/components/foursquare/__init__.py
+++ b/homeassistant/components/foursquare/__init__.py
@@ -12,7 +12,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_PUSH_SECRET = 'push_secret'
-DEPENDENCIES = ['http']
DOMAIN = 'foursquare'
EVENT_CHECKIN = 'foursquare.checkin'
diff --git a/homeassistant/components/foursquare/manifest.json b/homeassistant/components/foursquare/manifest.json
new file mode 100644
index 00000000000..84a98ca0336
--- /dev/null
+++ b/homeassistant/components/foursquare/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "foursquare",
+ "name": "Foursquare",
+ "documentation": "https://www.home-assistant.io/components/foursquare",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/free_mobile/manifest.json b/homeassistant/components/free_mobile/manifest.json
new file mode 100644
index 00000000000..b8a40c3fc1d
--- /dev/null
+++ b/homeassistant/components/free_mobile/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "free_mobile",
+ "name": "Free mobile",
+ "documentation": "https://www.home-assistant.io/components/free_mobile",
+ "requirements": [
+ "freesms==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/free_mobile/notify.py b/homeassistant/components/free_mobile/notify.py
index 1c6804f6d82..c7dacd44019 100644
--- a/homeassistant/components/free_mobile/notify.py
+++ b/homeassistant/components/free_mobile/notify.py
@@ -1,9 +1,4 @@
-"""
-Support for thr Free Mobile SMS platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.free_mobile/
-"""
+"""Support for thr Free Mobile SMS platform."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['freesms==0.1.2']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py
index 7accf7820f4..2cd9f6b3572 100644
--- a/homeassistant/components/freebox/__init__.py
+++ b/homeassistant/components/freebox/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ['aiofreepybox==0.0.8']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = "freebox"
diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py
index 5418c1c61a7..40c1967f60f 100644
--- a/homeassistant/components/freebox/device_tracker.py
+++ b/homeassistant/components/freebox/device_tracker.py
@@ -6,8 +6,6 @@ from homeassistant.components.device_tracker import DeviceScanner
from . import DATA_FREEBOX
-DEPENDENCIES = ['freebox']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/freebox/manifest.json b/homeassistant/components/freebox/manifest.json
new file mode 100644
index 00000000000..9ee134d4170
--- /dev/null
+++ b/homeassistant/components/freebox/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "freebox",
+ "name": "Freebox",
+ "documentation": "https://www.home-assistant.io/components/freebox",
+ "requirements": [
+ "aiofreepybox==0.0.8"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@snoof85"
+ ]
+}
diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py
index 328665ab51c..8dcc5f54b2e 100644
--- a/homeassistant/components/freebox/sensor.py
+++ b/homeassistant/components/freebox/sensor.py
@@ -5,8 +5,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_FREEBOX
-DEPENDENCIES = ['freebox']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py
index 4de194fc902..e0c24d2b9f9 100644
--- a/homeassistant/components/freebox/switch.py
+++ b/homeassistant/components/freebox/switch.py
@@ -5,8 +5,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DATA_FREEBOX
-DEPENDENCIES = ['freebox']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py
index edb3a57c28c..1986c932e22 100644
--- a/homeassistant/components/freedns/__init__.py
+++ b/homeassistant/components/freedns/__init__.py
@@ -1,21 +1,16 @@
-"""
-Integrate with FreeDNS Dynamic DNS service at freedns.afraid.org.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/freedns/
-"""
+"""Integrate with FreeDNS Dynamic DNS service at freedns.afraid.org."""
import asyncio
-from datetime import timedelta
import logging
+from datetime import timedelta
import aiohttp
import async_timeout
import voluptuous as vol
-from homeassistant.const import (CONF_URL, CONF_ACCESS_TOKEN,
- CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL,
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
import homeassistant.helpers.config_validation as cv
+from homeassistant.const import (
+ CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL, CONF_URL
+)
_LOGGER = logging.getLogger(__name__)
@@ -27,22 +22,12 @@ TIMEOUT = 10
UPDATE_URL = 'https://freedns.afraid.org/dynamic/update.php'
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(
- vol.Schema({
- vol.Exclusive(CONF_URL, DOMAIN): cv.string,
- vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=DEFAULT_INTERVAL
- )
- )
+ DOMAIN: vol.Schema({
+ vol.Exclusive(CONF_URL, DOMAIN): cv.string,
+ vol.Exclusive(CONF_ACCESS_TOKEN, DOMAIN): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
+ vol.All(cv.time_period, cv.positive_timedelta),
+ }),
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/freedns/manifest.json b/homeassistant/components/freedns/manifest.json
new file mode 100644
index 00000000000..63f929754db
--- /dev/null
+++ b/homeassistant/components/freedns/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "freedns",
+ "name": "Freedns",
+ "documentation": "https://www.home-assistant.io/components/freedns",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py
index 75e280fe908..fc9f65633ff 100644
--- a/homeassistant/components/fritz/device_tracker.py
+++ b/homeassistant/components/fritz/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for FRITZ!Box routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.fritz/
-"""
+"""Support for FRITZ!Box routers."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
-REQUIREMENTS = ['fritzconnection==0.6.5']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers.
diff --git a/homeassistant/components/fritz/manifest.json b/homeassistant/components/fritz/manifest.json
new file mode 100644
index 00000000000..b2aacbd48ad
--- /dev/null
+++ b/homeassistant/components/fritz/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fritz",
+ "name": "Fritz",
+ "documentation": "https://www.home-assistant.io/components/fritz",
+ "requirements": [
+ "fritzconnection==0.6.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py
index 81ba019acbc..610c6874140 100644
--- a/homeassistant/components/fritzbox/__init__.py
+++ b/homeassistant/components/fritzbox/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import discovery
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyfritzhome==0.4.0']
-
SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch', 'sensor']
DOMAIN = 'fritzbox'
diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py
index 65578c57180..a763a3b3b0e 100644
--- a/homeassistant/components/fritzbox/binary_sensor.py
+++ b/homeassistant/components/fritzbox/binary_sensor.py
@@ -7,8 +7,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import DOMAIN as FRITZBOX_DOMAIN
-DEPENDENCIES = ['fritzbox']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py
index e2c9be833ac..4dfa09c49fa 100644
--- a/homeassistant/components/fritzbox/climate.py
+++ b/homeassistant/components/fritzbox/climate.py
@@ -16,8 +16,6 @@ from . import (
ATTR_STATE_LOCKED, ATTR_STATE_SUMMER_MODE, ATTR_STATE_WINDOW_OPEN,
DOMAIN as FRITZBOX_DOMAIN)
-DEPENDENCIES = ['fritzbox']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
diff --git a/homeassistant/components/fritzbox/manifest.json b/homeassistant/components/fritzbox/manifest.json
new file mode 100644
index 00000000000..1ed18140bd2
--- /dev/null
+++ b/homeassistant/components/fritzbox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fritzbox",
+ "name": "Fritzbox",
+ "documentation": "https://www.home-assistant.io/components/fritzbox",
+ "requirements": [
+ "pyfritzhome==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py
index 7309f8cc618..123d8835318 100644
--- a/homeassistant/components/fritzbox/sensor.py
+++ b/homeassistant/components/fritzbox/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.helpers.entity import Entity
from . import (
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN)
-DEPENDENCIES = ['fritzbox']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py
index e227cdaef8a..ae1219cefda 100644
--- a/homeassistant/components/fritzbox/switch.py
+++ b/homeassistant/components/fritzbox/switch.py
@@ -10,8 +10,6 @@ from homeassistant.const import (
from . import (
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, DOMAIN as FRITZBOX_DOMAIN)
-DEPENDENCIES = ['fritzbox']
-
_LOGGER = logging.getLogger(__name__)
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
diff --git a/homeassistant/components/fritzbox_callmonitor/manifest.json b/homeassistant/components/fritzbox_callmonitor/manifest.json
new file mode 100644
index 00000000000..19f232ed667
--- /dev/null
+++ b/homeassistant/components/fritzbox_callmonitor/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fritzbox_callmonitor",
+ "name": "Fritzbox callmonitor",
+ "documentation": "https://www.home-assistant.io/components/fritzbox_callmonitor",
+ "requirements": [
+ "fritzconnection==0.6.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py
index 397f08d8a7c..95c0879996f 100644
--- a/homeassistant/components/fritzbox_callmonitor/sensor.py
+++ b/homeassistant/components/fritzbox_callmonitor/sensor.py
@@ -1,9 +1,4 @@
-"""
-A sensor to monitor incoming and outgoing phone calls on a Fritz!Box router.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fritzbox_callmonitor/
-"""
+"""Sensor to monitor incoming/outgoing phone calls on a Fritz!Box router."""
import logging
import socket
import threading
@@ -21,8 +16,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['fritzconnection==0.6.5']
-
_LOGGER = logging.getLogger(__name__)
CONF_PHONEBOOK = 'phonebook'
diff --git a/homeassistant/components/fritzbox_netmonitor/manifest.json b/homeassistant/components/fritzbox_netmonitor/manifest.json
new file mode 100644
index 00000000000..ac1ce2893e4
--- /dev/null
+++ b/homeassistant/components/fritzbox_netmonitor/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fritzbox_netmonitor",
+ "name": "Fritzbox netmonitor",
+ "documentation": "https://www.home-assistant.io/components/fritzbox_netmonitor",
+ "requirements": [
+ "fritzconnection==0.6.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py
index 356c1424012..ec8e38bb24b 100644
--- a/homeassistant/components/fritzbox_netmonitor/sensor.py
+++ b/homeassistant/components/fritzbox_netmonitor/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring an AVM Fritz!Box router.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fritzbox_netmonitor/
-"""
+"""Support for monitoring an AVM Fritz!Box router."""
import logging
from datetime import timedelta
from requests.exceptions import RequestException
@@ -16,8 +11,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['fritzconnection==0.6.5']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_NAME = 'fritz_netmonitor'
diff --git a/homeassistant/components/fritzdect/manifest.json b/homeassistant/components/fritzdect/manifest.json
new file mode 100644
index 00000000000..98d628fe078
--- /dev/null
+++ b/homeassistant/components/fritzdect/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "fritzdect",
+ "name": "Fritzdect",
+ "documentation": "https://www.home-assistant.io/components/fritzdect",
+ "requirements": [
+ "fritzhome==1.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/fritzdect/switch.py b/homeassistant/components/fritzdect/switch.py
index 0d9008552a1..d3cd00a73f5 100644
--- a/homeassistant/components/fritzdect/switch.py
+++ b/homeassistant/components/fritzdect/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for FRITZ!DECT Switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.fritzdect/
-"""
+"""Support for FRITZ!DECT Switches."""
import logging
from requests.exceptions import RequestException, HTTPError
@@ -16,8 +11,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
-REQUIREMENTS = ['fritzhome==1.0.4']
-
_LOGGER = logging.getLogger(__name__)
# Standard Fritz Box IP
diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index f0358dbd6cc..6f258b2d59c 100644
--- a/homeassistant/components/frontend/__init__.py
+++ b/homeassistant/components/frontend/__init__.py
@@ -21,12 +21,7 @@ from homeassistant.loader import bind_hass
from .storage import async_setup_frontend_storage
-REQUIREMENTS = ['home-assistant-frontend==20190331.0']
-
DOMAIN = 'frontend'
-DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
- 'auth', 'onboarding', 'lovelace']
-
CONF_THEMES = 'themes'
CONF_EXTRA_HTML_URL = 'extra_html_url'
CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5'
diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json
new file mode 100644
index 00000000000..608687610e4
--- /dev/null
+++ b/homeassistant/components/frontend/manifest.json
@@ -0,0 +1,20 @@
+{
+ "domain": "frontend",
+ "name": "Home Assistant Frontend",
+ "documentation": "https://www.home-assistant.io/components/frontend",
+ "requirements": [
+ "home-assistant-frontend==20190424.0"
+ ],
+ "dependencies": [
+ "api",
+ "auth",
+ "http",
+ "lovelace",
+ "onboarding",
+ "system_log",
+ "websocket_api"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/frontier_silicon/manifest.json b/homeassistant/components/frontier_silicon/manifest.json
new file mode 100644
index 00000000000..0e20a509d1f
--- /dev/null
+++ b/homeassistant/components/frontier_silicon/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "frontier_silicon",
+ "name": "Frontier silicon",
+ "documentation": "https://www.home-assistant.io/components/frontier_silicon",
+ "requirements": [
+ "afsapi==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py
index ed7041a3b82..64aa1d3a012 100644
--- a/homeassistant/components/frontier_silicon/media_player.py
+++ b/homeassistant/components/frontier_silicon/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Frontier Silicon Devices (Medion, Hama, Auna,...).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.frontier_silicon/
-"""
+"""Support for Frontier Silicon Devices (Medion, Hama, Auna,...)."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.const import (
STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['afsapi==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_FRONTIER_SILICON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
diff --git a/homeassistant/components/futurenow/light.py b/homeassistant/components/futurenow/light.py
index 8b0a809b667..91ec8b0794d 100644
--- a/homeassistant/components/futurenow/light.py
+++ b/homeassistant/components/futurenow/light.py
@@ -1,9 +1,4 @@
-"""
-Support for FutureNow Ethernet unit outputs as Lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.futurenow/
-"""
+"""Support for FutureNow Ethernet unit outputs as Lights."""
import logging
@@ -16,8 +11,6 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyfnip==0.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_DRIVER = 'driver'
diff --git a/homeassistant/components/futurenow/manifest.json b/homeassistant/components/futurenow/manifest.json
new file mode 100644
index 00000000000..5191ab611ac
--- /dev/null
+++ b/homeassistant/components/futurenow/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "futurenow",
+ "name": "Futurenow",
+ "documentation": "https://www.home-assistant.io/components/futurenow",
+ "requirements": [
+ "pyfnip==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py
index 426afc6d314..b3c7c7c1215 100644
--- a/homeassistant/components/garadget/cover.py
+++ b/homeassistant/components/garadget/cover.py
@@ -1,9 +1,4 @@
-"""
-Platform for the Garadget cover component.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/garadget/
-"""
+"""Platform for the Garadget cover component."""
import logging
import requests
diff --git a/homeassistant/components/garadget/manifest.json b/homeassistant/components/garadget/manifest.json
new file mode 100644
index 00000000000..d3781f81d04
--- /dev/null
+++ b/homeassistant/components/garadget/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "garadget",
+ "name": "Garadget",
+ "documentation": "https://www.home-assistant.io/components/garadget",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gc100/__init__.py b/homeassistant/components/gc100/__init__.py
index 36e9c61b1ba..b875d045cc0 100644
--- a/homeassistant/components/gc100/__init__.py
+++ b/homeassistant/components/gc100/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-gc100==1.0.3a']
-
_LOGGER = logging.getLogger(__name__)
CONF_PORTS = 'ports'
diff --git a/homeassistant/components/gc100/binary_sensor.py b/homeassistant/components/gc100/binary_sensor.py
index ec69d1eec83..4ba68a17799 100644
--- a/homeassistant/components/gc100/binary_sensor.py
+++ b/homeassistant/components/gc100/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for binary sensor using GC100.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.gc100/
-"""
+"""Support for binary sensor using GC100."""
import voluptuous as vol
from homeassistant.components.binary_sensor import (
@@ -13,8 +8,6 @@ import homeassistant.helpers.config_validation as cv
from . import CONF_PORTS, DATA_GC100
-DEPENDENCIES = ['gc100']
-
_SENSORS_SCHEMA = vol.Schema({
cv.string: cv.string,
})
diff --git a/homeassistant/components/gc100/manifest.json b/homeassistant/components/gc100/manifest.json
new file mode 100644
index 00000000000..96d792196ce
--- /dev/null
+++ b/homeassistant/components/gc100/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gc100",
+ "name": "Gc100",
+ "documentation": "https://www.home-assistant.io/components/gc100",
+ "requirements": [
+ "python-gc100==1.0.3a"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gc100/switch.py b/homeassistant/components/gc100/switch.py
index 94c824fa5d7..eea98a4dc23 100644
--- a/homeassistant/components/gc100/switch.py
+++ b/homeassistant/components/gc100/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for switches using GC100.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.gc100/
-"""
+"""Support for switches using GC100."""
import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA
@@ -13,8 +8,6 @@ from homeassistant.helpers.entity import ToggleEntity
from . import CONF_PORTS, DATA_GC100
-DEPENDENCIES = ['gc100']
-
_SWITCH_SCHEMA = vol.Schema({
cv.string: cv.string,
})
diff --git a/homeassistant/components/gearbest/manifest.json b/homeassistant/components/gearbest/manifest.json
new file mode 100644
index 00000000000..39ceca41d08
--- /dev/null
+++ b/homeassistant/components/gearbest/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "gearbest",
+ "name": "Gearbest",
+ "documentation": "https://www.home-assistant.io/components/gearbest",
+ "requirements": [
+ "gearbest_parser==1.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@HerrHofrat"
+ ]
+}
diff --git a/homeassistant/components/gearbest/sensor.py b/homeassistant/components/gearbest/sensor.py
index 5521e9a644c..ee0ee6d4e3b 100644
--- a/homeassistant/components/gearbest/sensor.py
+++ b/homeassistant/components/gearbest/sensor.py
@@ -1,9 +1,4 @@
-"""
-Parse prices of an item from gearbest.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.gearbest/
-"""
+"""Parse prices of an item from gearbest."""
import logging
from datetime import timedelta
@@ -16,7 +11,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_URL, CONF_CURRENCY)
-REQUIREMENTS = ['gearbest_parser==1.0.7']
_LOGGER = logging.getLogger(__name__)
CONF_ITEMS = 'items'
diff --git a/homeassistant/components/geizhals/manifest.json b/homeassistant/components/geizhals/manifest.json
new file mode 100644
index 00000000000..d53bceaa145
--- /dev/null
+++ b/homeassistant/components/geizhals/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "geizhals",
+ "name": "Geizhals",
+ "documentation": "https://www.home-assistant.io/components/geizhals",
+ "requirements": [
+ "geizhals==0.0.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py
index 66cab473f49..03c263f54ab 100644
--- a/homeassistant/components/geizhals/sensor.py
+++ b/homeassistant/components/geizhals/sensor.py
@@ -1,9 +1,4 @@
-"""
-Parse prices of a device from geizhals.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.geizhals/
-"""
+"""Parse prices of a device from geizhals."""
import logging
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_NAME
-REQUIREMENTS = ['geizhals==0.0.9']
-
_LOGGER = logging.getLogger(__name__)
CONF_DESCRIPTION = 'description'
diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py
index c9f8616f637..bfe42a5b080 100644
--- a/homeassistant/components/generic/camera.py
+++ b/homeassistant/components/generic/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for IP Cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.generic/
-"""
+"""Support for IP Cameras."""
import asyncio
import logging
diff --git a/homeassistant/components/generic/manifest.json b/homeassistant/components/generic/manifest.json
new file mode 100644
index 00000000000..e4d3622a562
--- /dev/null
+++ b/homeassistant/components/generic/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "generic",
+ "name": "Generic",
+ "documentation": "https://www.home-assistant.io/components/generic",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py
index 1eb0f8e79db..cfa8ba64ea5 100644
--- a/homeassistant/components/generic_thermostat/climate.py
+++ b/homeassistant/components/generic_thermostat/climate.py
@@ -1,9 +1,4 @@
-"""
-Adds support for generic thermostat units.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.generic_thermostat/
-"""
+"""Adds support for generic thermostat units."""
import asyncio
import logging
@@ -28,8 +23,6 @@ from homeassistant.components.climate.const import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['switch', 'sensor']
-
DEFAULT_TOLERANCE = 0.3
DEFAULT_NAME = 'Generic Thermostat'
diff --git a/homeassistant/components/generic_thermostat/manifest.json b/homeassistant/components/generic_thermostat/manifest.json
new file mode 100644
index 00000000000..41fb04c8456
--- /dev/null
+++ b/homeassistant/components/generic_thermostat/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "generic_thermostat",
+ "name": "Generic thermostat",
+ "documentation": "https://www.home-assistant.io/components/generic_thermostat",
+ "requirements": [],
+ "dependencies": [
+ "sensor",
+ "switch"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/generic_thermostat/services.yaml b/homeassistant/components/generic_thermostat/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py
new file mode 100644
index 00000000000..aa57af55852
--- /dev/null
+++ b/homeassistant/components/geniushub/__init__.py
@@ -0,0 +1,60 @@
+"""This module connects to a Genius hub and shares the data."""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ CONF_HOST, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME)
+from homeassistant.helpers import config_validation as cv
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.discovery import async_load_platform
+
+_LOGGER = logging.getLogger(__name__)
+
+DOMAIN = 'geniushub'
+
+_V1_API_SCHEMA = vol.Schema({
+ vol.Required(CONF_TOKEN): cv.string,
+})
+_V3_API_SCHEMA = vol.Schema({
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+})
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Any(
+ _V3_API_SCHEMA,
+ _V1_API_SCHEMA,
+ )
+}, extra=vol.ALLOW_EXTRA)
+
+
+async def async_setup(hass, hass_config):
+ """Create a Genius Hub system."""
+ from geniushubclient import GeniusHubClient # noqa; pylint: disable=no-name-in-module
+
+ geniushub_data = hass.data[DOMAIN] = {}
+
+ kwargs = dict(hass_config[DOMAIN])
+ if CONF_HOST in kwargs:
+ args = (kwargs.pop(CONF_HOST), )
+ else:
+ args = (kwargs.pop(CONF_TOKEN), )
+
+ try:
+ client = geniushub_data['client'] = GeniusHubClient(
+ *args, **kwargs, session=async_get_clientsession(hass)
+ )
+
+ await client.hub.update()
+
+ except AssertionError: # assert response.status == HTTP_OK
+ _LOGGER.warning(
+ "setup(): Failed, check your configuration.",
+ exc_info=True)
+ return False
+
+ hass.async_create_task(async_load_platform(
+ hass, 'climate', DOMAIN, {}, hass_config))
+
+ return True
diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py
new file mode 100644
index 00000000000..bc72b73c0ed
--- /dev/null
+++ b/homeassistant/components/geniushub/climate.py
@@ -0,0 +1,154 @@
+"""Supports Genius hub to provide climate controls."""
+import asyncio
+import logging
+
+from homeassistant.components.climate import ClimateDevice
+from homeassistant.components.climate.const import (
+ STATE_AUTO, STATE_ECO, STATE_HEAT, STATE_MANUAL,
+ SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_ON_OFF)
+from homeassistant.const import (
+ ATTR_TEMPERATURE, TEMP_CELSIUS)
+
+from . import DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+GENIUSHUB_SUPPORT_FLAGS = \
+ SUPPORT_TARGET_TEMPERATURE | \
+ SUPPORT_ON_OFF | \
+ SUPPORT_OPERATION_MODE
+
+GENIUSHUB_MAX_TEMP = 28.0
+GENIUSHUB_MIN_TEMP = 4.0
+
+# Genius supports only Off, Override/Boost, Footprint & Timer modes
+HA_OPMODE_TO_GH = {
+ STATE_AUTO: 'timer',
+ STATE_ECO: 'footprint',
+ STATE_MANUAL: 'override',
+}
+GH_OPMODE_OFF = 'off'
+GH_STATE_TO_HA = {
+ 'timer': STATE_AUTO,
+ 'footprint': STATE_ECO,
+ 'away': None,
+ 'override': STATE_MANUAL,
+ 'early': STATE_HEAT,
+ 'test': None,
+ 'linked': None,
+ 'other': None,
+} # intentionally missing 'off': None
+GH_DEVICE_STATE_ATTRS = ['temperature', 'type', 'occupied', 'override']
+
+
+async def async_setup_platform(hass, hass_config, async_add_entities,
+ discovery_info=None):
+ """Set up the Genius hub climate devices."""
+ client = hass.data[DOMAIN]['client']
+
+ zones = []
+ for zone in client.hub.zone_objs:
+ if hasattr(zone, 'temperature'):
+ zones.append(GeniusClimate(client, zone))
+
+ async_add_entities(zones)
+
+
+class GeniusClimate(ClimateDevice):
+ """Representation of a Genius Hub climate device."""
+
+ def __init__(self, client, zone):
+ """Initialize the climate device."""
+ self._client = client
+ self._objref = zone
+ self._id = zone.id
+ self._name = zone.name
+
+ # Only some zones have movement detectors, which allows footprint mode
+ op_list = list(HA_OPMODE_TO_GH)
+ if not hasattr(self._objref, 'occupied'):
+ op_list.remove(STATE_ECO)
+ self._operation_list = op_list
+
+ @property
+ def name(self):
+ """Return the name of the climate device."""
+ return self._objref.name
+
+ @property
+ def device_state_attributes(self):
+ """Return the device state attributes."""
+ tmp = self._objref.__dict__.items()
+ state = {k: v for k, v in tmp if k in GH_DEVICE_STATE_ATTRS}
+
+ return {'status': state}
+
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ return self._objref.temperature
+
+ @property
+ def target_temperature(self):
+ """Return the temperature we try to reach."""
+ return self._objref.setpoint
+
+ @property
+ def min_temp(self):
+ """Return max valid temperature that can be set."""
+ return GENIUSHUB_MIN_TEMP
+
+ @property
+ def max_temp(self):
+ """Return max valid temperature that can be set."""
+ return GENIUSHUB_MAX_TEMP
+
+ @property
+ def temperature_unit(self):
+ """Return the unit of measurement."""
+ return TEMP_CELSIUS
+
+ @property
+ def supported_features(self):
+ """Return the list of supported features."""
+ return GENIUSHUB_SUPPORT_FLAGS
+
+ @property
+ def operation_list(self):
+ """Return the list of available operation modes."""
+ return self._operation_list
+
+ @property
+ def current_operation(self):
+ """Return the current operation mode."""
+ return GH_STATE_TO_HA.get(self._objref.mode)
+
+ @property
+ def is_on(self):
+ """Return True if the device is on."""
+ return self._objref.mode in GH_STATE_TO_HA
+
+ async def async_set_operation_mode(self, operation_mode):
+ """Set a new operation mode for this zone."""
+ await self._objref.set_mode(HA_OPMODE_TO_GH.get(operation_mode))
+
+ async def async_set_temperature(self, **kwargs):
+ """Set a new target temperature for this zone."""
+ temperature = kwargs.get(ATTR_TEMPERATURE)
+ await self._objref.set_override(temperature, 3600) # 1 hour
+
+ async def async_turn_on(self):
+ """Turn on this heating zone."""
+ await self._objref.set_mode(HA_OPMODE_TO_GH.get(STATE_AUTO))
+
+ async def async_turn_off(self):
+ """Turn off this heating zone (i.e. to frost protect)."""
+ await self._objref.set_mode(GH_OPMODE_OFF)
+
+ async def async_update(self):
+ """Get the latest data from the hub."""
+ try:
+ await self._objref.update()
+ except (AssertionError, asyncio.TimeoutError) as err:
+ _LOGGER.warning("Update for %s failed, message: %s",
+ self._id, err)
diff --git a/homeassistant/components/geniushub/manifest.json b/homeassistant/components/geniushub/manifest.json
new file mode 100644
index 00000000000..4546be8078b
--- /dev/null
+++ b/homeassistant/components/geniushub/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "geniushub",
+ "name": "Genius Hub",
+ "documentation": "https://www.home-assistant.io/components/geniushub",
+ "requirements": [
+ "geniushub-client==0.3.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py
index e89616126d5..f7d79ae7145 100644
--- a/homeassistant/components/geo_json_events/geo_location.py
+++ b/homeassistant/components/geo_json_events/geo_location.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['geojson_client==0.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_EXTERNAL_ID = 'external_id'
diff --git a/homeassistant/components/geo_json_events/manifest.json b/homeassistant/components/geo_json_events/manifest.json
new file mode 100644
index 00000000000..8e4d7b8a7cd
--- /dev/null
+++ b/homeassistant/components/geo_json_events/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "geo_json_events",
+ "name": "Geo json events",
+ "documentation": "https://www.home-assistant.io/components/geo_json_events",
+ "requirements": [
+ "geojson_client==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/geo_location/manifest.json b/homeassistant/components/geo_location/manifest.json
new file mode 100644
index 00000000000..83b4241284e
--- /dev/null
+++ b/homeassistant/components/geo_location/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "geo_location",
+ "name": "Geo location",
+ "documentation": "https://www.home-assistant.io/components/geo_location",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/geo_rss_events/manifest.json b/homeassistant/components/geo_rss_events/manifest.json
new file mode 100644
index 00000000000..bce6758b0fe
--- /dev/null
+++ b/homeassistant/components/geo_rss_events/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "geo_rss_events",
+ "name": "Geo rss events",
+ "documentation": "https://www.home-assistant.io/components/geo_rss_events",
+ "requirements": [
+ "georss_generic_client==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py
index ab406f9241e..f900812385b 100644
--- a/homeassistant/components/geo_rss_events/sensor.py
+++ b/homeassistant/components/geo_rss_events/sensor.py
@@ -20,8 +20,6 @@ from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_URL)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['georss_client==0.5']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CATEGORY = 'category'
diff --git a/homeassistant/components/geofency/.translations/es.json b/homeassistant/components/geofency/.translations/es.json
index a81fc927b6b..04d5c01e03e 100644
--- a/homeassistant/components/geofency/.translations/es.json
+++ b/homeassistant/components/geofency/.translations/es.json
@@ -6,6 +6,13 @@
},
"create_entry": {
"default": "Para enviar eventos a Home Assistant, necesitar\u00e1s configurar la funci\u00f3n de webhook en Geofency.\n\nRellene la siguiente informaci\u00f3n:\n\n- URL: ``{webhook_url}``\n- M\u00e9todo: POST\n\nVer[la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles."
- }
+ },
+ "step": {
+ "user": {
+ "description": "\u00bfEst\u00e1s seguro de que quieres configurar el webhook de Geofency?",
+ "title": "Configurar el Webhook de Geofency"
+ }
+ },
+ "title": "Webhook de Geofency"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py
index f27798e9e0d..0b4b757ce9e 100644
--- a/homeassistant/components/geofency/__init__.py
+++ b/homeassistant/components/geofency/__init__.py
@@ -16,8 +16,6 @@ from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'geofency'
-DEPENDENCIES = ['webhook']
-
CONF_MOBILE_BEACONS = 'mobile_beacons'
CONFIG_SCHEMA = vol.Schema({
@@ -133,6 +131,11 @@ async def async_unload_entry(hass, entry):
await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER)
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'Geofency Webhook',
diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py
index 5e7b8291840..abccf610f5e 100644
--- a/homeassistant/components/geofency/device_tracker.py
+++ b/homeassistant/components/geofency/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for the Geofency device tracker platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.geofency/
-"""
+"""Support for the Geofency device tracker platform."""
import logging
from homeassistant.components.device_tracker import (
@@ -14,8 +9,6 @@ from . import DOMAIN as GEOFENCY_DOMAIN, TRACKER_UPDATE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['geofency']
-
DATA_KEY = '{}.{}'.format(GEOFENCY_DOMAIN, DEVICE_TRACKER_DOMAIN)
diff --git a/homeassistant/components/geofency/manifest.json b/homeassistant/components/geofency/manifest.json
new file mode 100644
index 00000000000..576d0e419a7
--- /dev/null
+++ b/homeassistant/components/geofency/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "geofency",
+ "name": "Geofency",
+ "documentation": "https://www.home-assistant.io/components/geofency",
+ "requirements": [],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/github/manifest.json b/homeassistant/components/github/manifest.json
new file mode 100644
index 00000000000..a2c2ae04376
--- /dev/null
+++ b/homeassistant/components/github/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "github",
+ "name": "Github",
+ "documentation": "https://www.home-assistant.io/components/github",
+ "requirements": [
+ "PyGithub==1.43.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py
index 335dbc668d9..d552d2c65cc 100644
--- a/homeassistant/components/github/sensor.py
+++ b/homeassistant/components/github/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for GitHub.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.github/
-"""
+"""Support for GitHub."""
from datetime import timedelta
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['PyGithub==1.43.5']
-
_LOGGER = logging.getLogger(__name__)
CONF_REPOS = 'repositories'
diff --git a/homeassistant/components/gitlab_ci/manifest.json b/homeassistant/components/gitlab_ci/manifest.json
new file mode 100644
index 00000000000..4ea04de9e02
--- /dev/null
+++ b/homeassistant/components/gitlab_ci/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gitlab_ci",
+ "name": "Gitlab ci",
+ "documentation": "https://www.home-assistant.io/components/gitlab_ci",
+ "requirements": [
+ "python-gitlab==1.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py
index 7f3b444bb75..54cbf34fdfc 100644
--- a/homeassistant/components/gitlab_ci/sensor.py
+++ b/homeassistant/components/gitlab_ci/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for retrieving latest GitLab CI job information.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.gitlab_ci/
-"""
+"""Sensor for retrieving latest GitLab CI job information."""
from datetime import timedelta
import logging
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['python-gitlab==1.6.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_BUILD_BRANCH = 'build branch'
diff --git a/homeassistant/components/gitter/manifest.json b/homeassistant/components/gitter/manifest.json
new file mode 100644
index 00000000000..6600e46a4ce
--- /dev/null
+++ b/homeassistant/components/gitter/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "gitter",
+ "name": "Gitter",
+ "documentation": "https://www.home-assistant.io/components/gitter",
+ "requirements": [
+ "gitterpy==0.1.7"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py
index 97cd3f662d5..06fb6e3a3b5 100644
--- a/homeassistant/components/gitter/sensor.py
+++ b/homeassistant/components/gitter/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for displaying details about a Gitter.im chat room.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.gitter/
-"""
+"""Support for displaying details about a Gitter.im chat room."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_ROOM
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['gitterpy==0.1.7']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MENTION = 'mention'
diff --git a/homeassistant/components/glances/manifest.json b/homeassistant/components/glances/manifest.json
new file mode 100644
index 00000000000..621bca8c430
--- /dev/null
+++ b/homeassistant/components/glances/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "glances",
+ "name": "Glances",
+ "documentation": "https://www.home-assistant.io/components/glances",
+ "requirements": [
+ "glances_api==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py
index 53db254e4b3..2a883e33da6 100644
--- a/homeassistant/components/glances/sensor.py
+++ b/homeassistant/components/glances/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support gathering system information of hosts which are running glances.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.glances/
-"""
+"""Support gathering system information of hosts which are running glances."""
from datetime import timedelta
import logging
@@ -12,15 +7,13 @@ import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_SSL,
- CONF_VERIFY_SSL, CONF_RESOURCES, TEMP_CELSIUS)
+ CONF_VERIFY_SSL, CONF_RESOURCES, STATE_UNAVAILABLE, TEMP_CELSIUS)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['glances_api==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_VERSION = 'version'
@@ -188,21 +181,34 @@ class GlancesSensor(Entity):
self._state = sensor['value']
elif self.type == 'docker_active':
count = 0
- for container in value['docker']['containers']:
- if container['Status'] == 'running' or \
- 'Up' in container['Status']:
- count += 1
- self._state = count
+ try:
+ for container in value['docker']['containers']:
+ if container['Status'] == 'running' or \
+ 'Up' in container['Status']:
+ count += 1
+ self._state = count
+ except KeyError:
+ self._state = count
elif self.type == 'docker_cpu_use':
- use = 0.0
- for container in value['docker']['containers']:
- use += container['cpu']['total']
- self._state = round(use, 1)
+ cpu_use = 0.0
+ try:
+ for container in value['docker']['containers']:
+ if container['Status'] == 'running' or \
+ 'Up' in container['Status']:
+ cpu_use += container['cpu']['total']
+ self._state = round(cpu_use, 1)
+ except KeyError:
+ self._state = STATE_UNAVAILABLE
elif self.type == 'docker_memory_use':
- use = 0.0
- for container in value['docker']['containers']:
- use += container['memory']['usage']
- self._state = round(use / 1024**2, 1)
+ mem_use = 0.0
+ try:
+ for container in value['docker']['containers']:
+ if container['Status'] == 'running' or \
+ 'Up' in container['Status']:
+ mem_use += container['memory']['usage']
+ self._state = round(mem_use / 1024**2, 1)
+ except KeyError:
+ self._state = STATE_UNAVAILABLE
class GlancesData:
diff --git a/homeassistant/components/gntp/manifest.json b/homeassistant/components/gntp/manifest.json
new file mode 100644
index 00000000000..7315e3c7c84
--- /dev/null
+++ b/homeassistant/components/gntp/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "gntp",
+ "name": "Gntp",
+ "documentation": "https://www.home-assistant.io/components/gntp",
+ "requirements": [
+ "gntp==1.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/gntp/notify.py b/homeassistant/components/gntp/notify.py
index 915d33668b4..005043c1384 100644
--- a/homeassistant/components/gntp/notify.py
+++ b/homeassistant/components/gntp/notify.py
@@ -1,9 +1,4 @@
-"""
-GNTP (aka Growl) notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.gntp/
-"""
+"""GNTP (aka Growl) notification service."""
import logging
import os
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService)
-REQUIREMENTS = ['gntp==1.0.3']
-
_LOGGER = logging.getLogger(__name__)
_GNTP_LOGGER = logging.getLogger('gntp')
diff --git a/homeassistant/components/goalfeed/__init__.py b/homeassistant/components/goalfeed/__init__.py
index 6f0149f657a..4a7e4ea980a 100644
--- a/homeassistant/components/goalfeed/__init__.py
+++ b/homeassistant/components/goalfeed/__init__.py
@@ -9,7 +9,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
# Version downgraded due to regression in library
# For details: https://github.com/nlsdfnbch/Pysher/issues/38
-REQUIREMENTS = ['pysher==1.0.1']
DOMAIN = 'goalfeed'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/goalfeed/manifest.json b/homeassistant/components/goalfeed/manifest.json
new file mode 100644
index 00000000000..861abe0b462
--- /dev/null
+++ b/homeassistant/components/goalfeed/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "goalfeed",
+ "name": "Goalfeed",
+ "documentation": "https://www.home-assistant.io/components/goalfeed",
+ "requirements": [
+ "pysher==1.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py
index accc4f9ec98..610c131bda5 100644
--- a/homeassistant/components/gogogate2/cover.py
+++ b/homeassistant/components/gogogate2/cover.py
@@ -1,9 +1,4 @@
-"""
-Support for Gogogate2 garage Doors.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/cover.gogogate2/
-"""
+"""Support for Gogogate2 garage Doors."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import (
CONF_IP_ADDRESS, CONF_NAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pygogogate2==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'gogogate2'
diff --git a/homeassistant/components/gogogate2/manifest.json b/homeassistant/components/gogogate2/manifest.json
new file mode 100644
index 00000000000..3f3f2c25d0c
--- /dev/null
+++ b/homeassistant/components/gogogate2/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gogogate2",
+ "name": "Gogogate2",
+ "documentation": "https://www.home-assistant.io/components/gogogate2",
+ "requirements": [
+ "pygogogate2==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py
index 8fba016df57..e9bbf3f96cd 100644
--- a/homeassistant/components/google/__init__.py
+++ b/homeassistant/components/google/__init__.py
@@ -13,12 +13,6 @@ from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_time_change
from homeassistant.util import convert, dt
-REQUIREMENTS = [
- 'google-api-python-client==1.6.4',
- 'httplib2==0.10.3',
- 'oauth2client==4.0.0',
-]
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'google'
@@ -36,6 +30,7 @@ CONF_TRACK = 'track'
CONF_SEARCH = 'search'
CONF_OFFSET = 'offset'
CONF_IGNORE_AVAILABILITY = 'ignore_availability'
+CONF_MAX_RESULTS = 'max_results'
DEFAULT_CONF_TRACK_NEW = True
DEFAULT_CONF_OFFSET = '!!'
@@ -69,6 +64,7 @@ _SINGLE_CALSEARCH_CONFIG = vol.Schema({
vol.Optional(CONF_OFFSET): cv.string,
vol.Optional(CONF_SEARCH): cv.string,
vol.Optional(CONF_TRACK): cv.boolean,
+ vol.Optional(CONF_MAX_RESULTS): cv.positive_int,
})
DEVICE_SCHEMA = vol.Schema({
@@ -151,6 +147,9 @@ def setup(hass, config):
hass.data[DATA_INDEX] = {}
conf = config.get(DOMAIN, {})
+ if not conf:
+ # component is set up by tts platform
+ return True
token_file = hass.config.path(TOKEN_FILE)
if not os.path.isfile(token_file):
diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py
index 9f71e7c4f20..36ab3459d5c 100644
--- a/homeassistant/components/google/calendar.py
+++ b/homeassistant/components/google/calendar.py
@@ -7,7 +7,7 @@ from homeassistant.util import Throttle, dt
from . import (
CONF_CAL_ID, CONF_ENTITIES, CONF_IGNORE_AVAILABILITY, CONF_SEARCH,
- CONF_TRACK, TOKEN_FILE, GoogleCalendarService)
+ CONF_TRACK, TOKEN_FILE, CONF_MAX_RESULTS, GoogleCalendarService)
_LOGGER = logging.getLogger(__name__)
@@ -41,7 +41,8 @@ class GoogleCalendarEventDevice(CalendarEventDevice):
"""Create the Calendar event device."""
self.data = GoogleCalendarData(calendar_service, calendar,
data.get(CONF_SEARCH),
- data.get(CONF_IGNORE_AVAILABILITY))
+ data.get(CONF_IGNORE_AVAILABILITY),
+ data.get(CONF_MAX_RESULTS))
super().__init__(hass, data)
@@ -54,12 +55,13 @@ class GoogleCalendarData:
"""Class to utilize calendar service object to get next event."""
def __init__(self, calendar_service, calendar_id, search,
- ignore_availability):
+ ignore_availability, max_results):
"""Set up how we are going to search the google calendar."""
self.calendar_service = calendar_service
self.calendar_id = calendar_id
self.search = search
self.ignore_availability = ignore_availability
+ self.max_results = max_results
self.event = None
def _prepare_query(self):
@@ -73,6 +75,8 @@ class GoogleCalendarData:
return False
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['calendarId'] = self.calendar_id
+ if self.max_results:
+ params['max_results'] = self.max_results
if self.search:
params['q'] = self.search
diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json
new file mode 100644
index 00000000000..4c7e82ecfef
--- /dev/null
+++ b/homeassistant/components/google/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "google",
+ "name": "Google",
+ "documentation": "https://www.home-assistant.io/components/google",
+ "requirements": [
+ "google-api-python-client==1.6.4",
+ "httplib2==0.10.3",
+ "oauth2client==4.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py
index 0fd167c2729..c8078b7d9d2 100644
--- a/homeassistant/components/google_assistant/__init__.py
+++ b/homeassistant/components/google_assistant/__init__.py
@@ -20,7 +20,7 @@ from .const import (
CONF_EXPOSED_DOMAINS, DEFAULT_EXPOSED_DOMAINS, CONF_API_KEY,
SERVICE_REQUEST_SYNC, REQUEST_SYNC_BASE_URL, CONF_ENTITY_CONFIG,
CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT, CONF_ALLOW_UNLOCK,
- DEFAULT_ALLOW_UNLOCK
+ CONF_SECURE_DEVICES_PIN
)
from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401
from .const import EVENT_QUERY_RECEIVED # noqa: F401
@@ -28,8 +28,6 @@ from .http import async_register_http
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
-
ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_EXPOSE): cv.boolean,
@@ -37,17 +35,20 @@ ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_ROOM_HINT): cv.string,
})
-GOOGLE_ASSISTANT_SCHEMA = vol.Schema({
- vol.Required(CONF_PROJECT_ID): cv.string,
- vol.Optional(CONF_EXPOSE_BY_DEFAULT,
- default=DEFAULT_EXPOSE_BY_DEFAULT): cv.boolean,
- vol.Optional(CONF_EXPOSED_DOMAINS,
- default=DEFAULT_EXPOSED_DOMAINS): cv.ensure_list,
- vol.Optional(CONF_API_KEY): cv.string,
- vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA},
- vol.Optional(CONF_ALLOW_UNLOCK,
- default=DEFAULT_ALLOW_UNLOCK): cv.boolean,
-}, extra=vol.PREVENT_EXTRA)
+GOOGLE_ASSISTANT_SCHEMA = vol.All(
+ cv.deprecated(CONF_ALLOW_UNLOCK, invalidation_version='0.95'),
+ vol.Schema({
+ vol.Required(CONF_PROJECT_ID): cv.string,
+ vol.Optional(CONF_EXPOSE_BY_DEFAULT,
+ default=DEFAULT_EXPOSE_BY_DEFAULT): cv.boolean,
+ vol.Optional(CONF_EXPOSED_DOMAINS,
+ default=DEFAULT_EXPOSED_DOMAINS): cv.ensure_list,
+ vol.Optional(CONF_API_KEY): cv.string,
+ vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA},
+ vol.Optional(CONF_ALLOW_UNLOCK): cv.boolean,
+ # str on purpose, makes sure it is configured correctly.
+ vol.Optional(CONF_SECURE_DEVICES_PIN): str,
+ }, extra=vol.PREVENT_EXTRA))
CONFIG_SCHEMA = vol.Schema({
DOMAIN: GOOGLE_ASSISTANT_SCHEMA
diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py
index 852ea2469a2..1bab27bdd12 100644
--- a/homeassistant/components/google_assistant/const.py
+++ b/homeassistant/components/google_assistant/const.py
@@ -1,4 +1,20 @@
"""Constants for Google Assistant."""
+from homeassistant.components import (
+ binary_sensor,
+ camera,
+ climate,
+ cover,
+ fan,
+ group,
+ input_boolean,
+ light,
+ lock,
+ media_player,
+ scene,
+ script,
+ switch,
+ vacuum,
+)
DOMAIN = 'google_assistant'
GOOGLE_ASSISTANT_API_ENDPOINT = '/api/google_assistant'
@@ -12,13 +28,14 @@ CONF_ALIASES = 'aliases'
CONF_API_KEY = 'api_key'
CONF_ROOM_HINT = 'room'
CONF_ALLOW_UNLOCK = 'allow_unlock'
+CONF_SECURE_DEVICES_PIN = 'secure_devices_pin'
DEFAULT_EXPOSE_BY_DEFAULT = True
DEFAULT_EXPOSED_DOMAINS = [
'climate', 'cover', 'fan', 'group', 'input_boolean', 'light',
'media_player', 'scene', 'script', 'switch', 'vacuum', 'lock',
+ 'binary_sensor', 'sensor'
]
-DEFAULT_ALLOW_UNLOCK = False
PREFIX_TYPES = 'action.devices.types.'
TYPE_CAMERA = PREFIX_TYPES + 'CAMERA'
@@ -30,13 +47,17 @@ TYPE_FAN = PREFIX_TYPES + 'FAN'
TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT'
TYPE_LOCK = PREFIX_TYPES + 'LOCK'
TYPE_BLINDS = PREFIX_TYPES + 'BLINDS'
+TYPE_GARAGE = PREFIX_TYPES + 'GARAGE'
+TYPE_OUTLET = PREFIX_TYPES + 'OUTLET'
+TYPE_SENSOR = PREFIX_TYPES + 'SENSOR'
+TYPE_DOOR = PREFIX_TYPES + 'DOOR'
SERVICE_REQUEST_SYNC = 'request_sync'
HOMEGRAPH_URL = 'https://homegraph.googleapis.com/'
REQUEST_SYNC_BASE_URL = HOMEGRAPH_URL + 'v1/devices:requestSync'
# Error codes used for SmartHomeError class
-# https://developers.google.com/actions/smarthome/create-app#error_responses
+# https://developers.google.com/actions/reference/smarthome/errors-exceptions
ERR_DEVICE_OFFLINE = "deviceOffline"
ERR_DEVICE_NOT_FOUND = "deviceNotFound"
ERR_VALUE_OUT_OF_RANGE = "valueOutOfRange"
@@ -45,7 +66,46 @@ ERR_PROTOCOL_ERROR = 'protocolError'
ERR_UNKNOWN_ERROR = 'unknownError'
ERR_FUNCTION_NOT_SUPPORTED = 'functionNotSupported'
+ERR_CHALLENGE_NEEDED = 'challengeNeeded'
+ERR_CHALLENGE_NOT_SETUP = 'challengeFailedNotSetup'
+ERR_TOO_MANY_FAILED_ATTEMPTS = 'tooManyFailedAttempts'
+ERR_PIN_INCORRECT = 'pinIncorrect'
+ERR_USER_CANCELLED = 'userCancelled'
+
# Event types
EVENT_COMMAND_RECEIVED = 'google_assistant_command'
EVENT_QUERY_RECEIVED = 'google_assistant_query'
EVENT_SYNC_RECEIVED = 'google_assistant_sync'
+
+DOMAIN_TO_GOOGLE_TYPES = {
+ camera.DOMAIN: TYPE_CAMERA,
+ climate.DOMAIN: TYPE_THERMOSTAT,
+ cover.DOMAIN: TYPE_BLINDS,
+ fan.DOMAIN: TYPE_FAN,
+ group.DOMAIN: TYPE_SWITCH,
+ input_boolean.DOMAIN: TYPE_SWITCH,
+ light.DOMAIN: TYPE_LIGHT,
+ lock.DOMAIN: TYPE_LOCK,
+ media_player.DOMAIN: TYPE_SWITCH,
+ scene.DOMAIN: TYPE_SCENE,
+ script.DOMAIN: TYPE_SCENE,
+ switch.DOMAIN: TYPE_SWITCH,
+ vacuum.DOMAIN: TYPE_VACUUM,
+}
+
+DEVICE_CLASS_TO_GOOGLE_TYPES = {
+ (cover.DOMAIN, cover.DEVICE_CLASS_GARAGE): TYPE_GARAGE,
+ (cover.DOMAIN, cover.DEVICE_CLASS_DOOR): TYPE_DOOR,
+ (switch.DOMAIN, switch.DEVICE_CLASS_SWITCH): TYPE_SWITCH,
+ (switch.DOMAIN, switch.DEVICE_CLASS_OUTLET): TYPE_OUTLET,
+ (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_DOOR): TYPE_DOOR,
+ (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_GARAGE_DOOR):
+ TYPE_SENSOR,
+ (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_LOCK): TYPE_SENSOR,
+ (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_OPENING): TYPE_SENSOR,
+ (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_WINDOW): TYPE_SENSOR,
+}
+
+CHALLENGE_ACK_NEEDED = 'ackNeeded'
+CHALLENGE_PIN_NEEDED = 'pinNeeded'
+CHALLENGE_FAILED_PIN_NEEDED = 'challengeFailedPinNeeded'
diff --git a/homeassistant/components/google_assistant/error.py b/homeassistant/components/google_assistant/error.py
new file mode 100644
index 00000000000..3aef1e9408d
--- /dev/null
+++ b/homeassistant/components/google_assistant/error.py
@@ -0,0 +1,42 @@
+"""Errors for Google Assistant."""
+from .const import ERR_CHALLENGE_NEEDED
+
+
+class SmartHomeError(Exception):
+ """Google Assistant Smart Home errors.
+
+ https://developers.google.com/actions/smarthome/create-app#error_responses
+ """
+
+ def __init__(self, code, msg):
+ """Log error code."""
+ super().__init__(msg)
+ self.code = code
+
+ def to_response(self):
+ """Convert to a response format."""
+ return {
+ 'errorCode': self.code
+ }
+
+
+class ChallengeNeeded(SmartHomeError):
+ """Google Assistant Smart Home errors.
+
+ https://developers.google.com/actions/smarthome/create-app#error_responses
+ """
+
+ def __init__(self, challenge_type):
+ """Initialize challenge needed error."""
+ super().__init__(ERR_CHALLENGE_NEEDED,
+ 'Challenge needed: {}'.format(challenge_type))
+ self.challenge_type = challenge_type
+
+ def to_response(self):
+ """Convert to a response format."""
+ return {
+ 'errorCode': self.code,
+ 'challengeNeeded': {
+ 'type': self.challenge_type
+ }
+ }
diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py
index 8afa55acc5c..71cce9de500 100644
--- a/homeassistant/components/google_assistant/helpers.py
+++ b/homeassistant/components/google_assistant/helpers.py
@@ -1,28 +1,30 @@
"""Helper classes for Google Assistant integration."""
-from homeassistant.core import Context
+from asyncio import gather
+from collections.abc import Mapping
+from homeassistant.core import Context, callback
+from homeassistant.const import (
+ CONF_NAME, STATE_UNAVAILABLE, ATTR_SUPPORTED_FEATURES,
+ ATTR_DEVICE_CLASS
+)
-class SmartHomeError(Exception):
- """Google Assistant Smart Home errors.
-
- https://developers.google.com/actions/smarthome/create-app#error_responses
- """
-
- def __init__(self, code, msg):
- """Log error code."""
- super().__init__(msg)
- self.code = code
+from . import trait
+from .const import (
+ DOMAIN_TO_GOOGLE_TYPES, CONF_ALIASES, ERR_FUNCTION_NOT_SUPPORTED,
+ DEVICE_CLASS_TO_GOOGLE_TYPES, CONF_ROOM_HINT,
+)
+from .error import SmartHomeError
class Config:
"""Hold the configuration for Google Assistant."""
- def __init__(self, should_expose, allow_unlock,
- entity_config=None):
+ def __init__(self, should_expose,
+ entity_config=None, secure_devices_pin=None):
"""Initialize the configuration."""
self.should_expose = should_expose
self.entity_config = entity_config or {}
- self.allow_unlock = allow_unlock
+ self.secure_devices_pin = secure_devices_pin
class RequestData:
@@ -33,3 +35,177 @@ class RequestData:
self.config = config
self.request_id = request_id
self.context = Context(user_id=user_id)
+
+
+def get_google_type(domain, device_class):
+ """Google type based on domain and device class."""
+ typ = DEVICE_CLASS_TO_GOOGLE_TYPES.get((domain, device_class))
+
+ return typ if typ is not None else DOMAIN_TO_GOOGLE_TYPES[domain]
+
+
+class GoogleEntity:
+ """Adaptation of Entity expressed in Google's terms."""
+
+ def __init__(self, hass, config, state):
+ """Initialize a Google entity."""
+ self.hass = hass
+ self.config = config
+ self.state = state
+ self._traits = None
+
+ @property
+ def entity_id(self):
+ """Return entity ID."""
+ return self.state.entity_id
+
+ @callback
+ def traits(self):
+ """Return traits for entity."""
+ if self._traits is not None:
+ return self._traits
+
+ state = self.state
+ domain = state.domain
+ features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
+ device_class = state.attributes.get(ATTR_DEVICE_CLASS)
+
+ self._traits = [Trait(self.hass, state, self.config)
+ for Trait in trait.TRAITS
+ if Trait.supported(domain, features, device_class)]
+ return self._traits
+
+ async def sync_serialize(self):
+ """Serialize entity for a SYNC response.
+
+ https://developers.google.com/actions/smarthome/create-app#actiondevicessync
+ """
+ state = self.state
+
+ # When a state is unavailable, the attributes that describe
+ # capabilities will be stripped. For example, a light entity will miss
+ # the min/max mireds. Therefore they will be excluded from a sync.
+ if state.state == STATE_UNAVAILABLE:
+ return None
+
+ entity_config = self.config.entity_config.get(state.entity_id, {})
+ name = (entity_config.get(CONF_NAME) or state.name).strip()
+ domain = state.domain
+ device_class = state.attributes.get(ATTR_DEVICE_CLASS)
+
+ # If an empty string
+ if not name:
+ return None
+
+ traits = self.traits()
+
+ # Found no supported traits for this entity
+ if not traits:
+ return None
+
+ device_type = get_google_type(domain,
+ device_class)
+
+ device = {
+ 'id': state.entity_id,
+ 'name': {
+ 'name': name
+ },
+ 'attributes': {},
+ 'traits': [trait.name for trait in traits],
+ 'willReportState': False,
+ 'type': device_type,
+ }
+
+ # use aliases
+ aliases = entity_config.get(CONF_ALIASES)
+ if aliases:
+ device['name']['nicknames'] = aliases
+
+ for trt in traits:
+ device['attributes'].update(trt.sync_attributes())
+
+ room = entity_config.get(CONF_ROOM_HINT)
+ if room:
+ device['roomHint'] = room
+ return device
+
+ dev_reg, ent_reg, area_reg = await gather(
+ self.hass.helpers.device_registry.async_get_registry(),
+ self.hass.helpers.entity_registry.async_get_registry(),
+ self.hass.helpers.area_registry.async_get_registry(),
+ )
+
+ entity_entry = ent_reg.async_get(state.entity_id)
+ if not (entity_entry and entity_entry.device_id):
+ return device
+
+ device_entry = dev_reg.devices.get(entity_entry.device_id)
+ if not (device_entry and device_entry.area_id):
+ return device
+
+ area_entry = area_reg.areas.get(device_entry.area_id)
+ if area_entry and area_entry.name:
+ device['roomHint'] = area_entry.name
+
+ return device
+
+ @callback
+ def query_serialize(self):
+ """Serialize entity for a QUERY response.
+
+ https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
+ """
+ state = self.state
+
+ if state.state == STATE_UNAVAILABLE:
+ return {'online': False}
+
+ attrs = {'online': True}
+
+ for trt in self.traits():
+ deep_update(attrs, trt.query_attributes())
+
+ return attrs
+
+ async def execute(self, data, command_payload):
+ """Execute a command.
+
+ https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
+ """
+ command = command_payload['command']
+ params = command_payload.get('params', {})
+ challenge = command_payload.get('challenge', {})
+ executed = False
+ for trt in self.traits():
+ if trt.can_execute(command, params):
+ await trt.execute(command, data, params, challenge)
+ executed = True
+ break
+
+ if not executed:
+ raise SmartHomeError(
+ ERR_FUNCTION_NOT_SUPPORTED,
+ 'Unable to execute {} for {}'.format(command,
+ self.state.entity_id))
+
+ @callback
+ def async_update(self):
+ """Update the entity with latest info from Home Assistant."""
+ self.state = self.hass.states.get(self.entity_id)
+
+ if self._traits is None:
+ return
+
+ for trt in self._traits:
+ trt.state = self.state
+
+
+def deep_update(target, source):
+ """Update a nested dictionary with another nested dictionary."""
+ for key, value in source.items():
+ if isinstance(value, Mapping):
+ target[key] = deep_update(target.get(key, {}), value)
+ else:
+ target[key] = value
+ return target
diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py
index cbe2015f4f9..d385d742c7d 100644
--- a/homeassistant/components/google_assistant/http.py
+++ b/homeassistant/components/google_assistant/http.py
@@ -1,9 +1,4 @@
-"""
-Support for Google Actions Smart Home Control.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/google_assistant/
-"""
+"""Support for Google Actions Smart Home Control."""
import logging
from aiohttp.web import Request, Response
@@ -15,12 +10,12 @@ from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from .const import (
GOOGLE_ASSISTANT_API_ENDPOINT,
- CONF_ALLOW_UNLOCK,
CONF_EXPOSE_BY_DEFAULT,
CONF_EXPOSED_DOMAINS,
CONF_ENTITY_CONFIG,
CONF_EXPOSE,
- )
+ CONF_SECURE_DEVICES_PIN,
+)
from .smart_home import async_handle_message
from .helpers import Config
@@ -33,7 +28,7 @@ def async_register_http(hass, cfg):
expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS)
entity_config = cfg.get(CONF_ENTITY_CONFIG) or {}
- allow_unlock = cfg.get(CONF_ALLOW_UNLOCK, False)
+ secure_devices_pin = cfg.get(CONF_SECURE_DEVICES_PIN)
def is_exposed(entity) -> bool:
"""Determine if an entity should be exposed to Google Assistant."""
@@ -58,8 +53,13 @@ def async_register_http(hass, cfg):
return is_default_exposed or explicit_expose
- hass.http.register_view(
- GoogleAssistantView(is_exposed, entity_config, allow_unlock))
+ config = Config(
+ should_expose=is_exposed,
+ entity_config=entity_config,
+ secure_devices_pin=secure_devices_pin
+ )
+
+ hass.http.register_view(GoogleAssistantView(config))
class GoogleAssistantView(HomeAssistantView):
@@ -69,11 +69,9 @@ class GoogleAssistantView(HomeAssistantView):
name = 'api:google_assistant'
requires_auth = True
- def __init__(self, is_exposed, entity_config, allow_unlock):
+ def __init__(self, config):
"""Initialize the Google Assistant request handler."""
- self.config = Config(is_exposed,
- allow_unlock,
- entity_config)
+ self.config = config
async def post(self, request: Request) -> Response:
"""Handle Google Assistant requests."""
diff --git a/homeassistant/components/google_assistant/manifest.json b/homeassistant/components/google_assistant/manifest.json
new file mode 100644
index 00000000000..ff916930216
--- /dev/null
+++ b/homeassistant/components/google_assistant/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "google_assistant",
+ "name": "Google assistant",
+ "documentation": "https://www.home-assistant.io/components/google_assistant",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py
index d84c8037c60..37f35edf645 100644
--- a/homeassistant/components/google_assistant/smart_home.py
+++ b/homeassistant/components/google_assistant/smart_home.py
@@ -1,220 +1,22 @@
"""Support for Google Assistant Smart Home API."""
-from asyncio import gather
-from collections.abc import Mapping
from itertools import product
import logging
from homeassistant.util.decorator import Registry
-from homeassistant.core import callback
from homeassistant.const import (
- CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE,
- ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID,
-)
-from homeassistant.components import (
- camera,
- climate,
- cover,
- fan,
- group,
- input_boolean,
- light,
- lock,
- media_player,
- scene,
- script,
- switch,
- vacuum,
-)
+ CLOUD_NEVER_EXPOSED_ENTITIES, ATTR_ENTITY_ID)
-
-from . import trait
from .const import (
- TYPE_LIGHT, TYPE_LOCK, TYPE_SCENE, TYPE_SWITCH, TYPE_VACUUM,
- TYPE_THERMOSTAT, TYPE_FAN, TYPE_CAMERA, TYPE_BLINDS,
- CONF_ALIASES, CONF_ROOM_HINT,
- ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE,
- ERR_UNKNOWN_ERROR,
+ ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, ERR_UNKNOWN_ERROR,
EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED, EVENT_QUERY_RECEIVED
)
-from .helpers import SmartHomeError, RequestData
+from .helpers import RequestData, GoogleEntity
+from .error import SmartHomeError
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
-DOMAIN_TO_GOOGLE_TYPES = {
- camera.DOMAIN: TYPE_CAMERA,
- climate.DOMAIN: TYPE_THERMOSTAT,
- cover.DOMAIN: TYPE_BLINDS,
- fan.DOMAIN: TYPE_FAN,
- group.DOMAIN: TYPE_SWITCH,
- input_boolean.DOMAIN: TYPE_SWITCH,
- light.DOMAIN: TYPE_LIGHT,
- lock.DOMAIN: TYPE_LOCK,
- media_player.DOMAIN: TYPE_SWITCH,
- scene.DOMAIN: TYPE_SCENE,
- script.DOMAIN: TYPE_SCENE,
- switch.DOMAIN: TYPE_SWITCH,
- vacuum.DOMAIN: TYPE_VACUUM,
-}
-
-
-def deep_update(target, source):
- """Update a nested dictionary with another nested dictionary."""
- for key, value in source.items():
- if isinstance(value, Mapping):
- target[key] = deep_update(target.get(key, {}), value)
- else:
- target[key] = value
- return target
-
-
-class _GoogleEntity:
- """Adaptation of Entity expressed in Google's terms."""
-
- def __init__(self, hass, config, state):
- self.hass = hass
- self.config = config
- self.state = state
- self._traits = None
-
- @property
- def entity_id(self):
- """Return entity ID."""
- return self.state.entity_id
-
- @callback
- def traits(self):
- """Return traits for entity."""
- if self._traits is not None:
- return self._traits
-
- state = self.state
- domain = state.domain
- features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
-
- self._traits = [Trait(self.hass, state, self.config)
- for Trait in trait.TRAITS
- if Trait.supported(domain, features)]
- return self._traits
-
- async def sync_serialize(self):
- """Serialize entity for a SYNC response.
-
- https://developers.google.com/actions/smarthome/create-app#actiondevicessync
- """
- state = self.state
-
- # When a state is unavailable, the attributes that describe
- # capabilities will be stripped. For example, a light entity will miss
- # the min/max mireds. Therefore they will be excluded from a sync.
- if state.state == STATE_UNAVAILABLE:
- return None
-
- entity_config = self.config.entity_config.get(state.entity_id, {})
- name = (entity_config.get(CONF_NAME) or state.name).strip()
-
- # If an empty string
- if not name:
- return None
-
- traits = self.traits()
-
- # Found no supported traits for this entity
- if not traits:
- return None
-
- device = {
- 'id': state.entity_id,
- 'name': {
- 'name': name
- },
- 'attributes': {},
- 'traits': [trait.name for trait in traits],
- 'willReportState': False,
- 'type': DOMAIN_TO_GOOGLE_TYPES[state.domain],
- }
-
- # use aliases
- aliases = entity_config.get(CONF_ALIASES)
- if aliases:
- device['name']['nicknames'] = aliases
-
- for trt in traits:
- device['attributes'].update(trt.sync_attributes())
-
- room = entity_config.get(CONF_ROOM_HINT)
- if room:
- device['roomHint'] = room
- return device
-
- dev_reg, ent_reg, area_reg = await gather(
- self.hass.helpers.device_registry.async_get_registry(),
- self.hass.helpers.entity_registry.async_get_registry(),
- self.hass.helpers.area_registry.async_get_registry(),
- )
-
- entity_entry = ent_reg.async_get(state.entity_id)
- if not (entity_entry and entity_entry.device_id):
- return device
-
- device_entry = dev_reg.devices.get(entity_entry.device_id)
- if not (device_entry and device_entry.area_id):
- return device
-
- area_entry = area_reg.areas.get(device_entry.area_id)
- if area_entry and area_entry.name:
- device['roomHint'] = area_entry.name
-
- return device
-
- @callback
- def query_serialize(self):
- """Serialize entity for a QUERY response.
-
- https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
- """
- state = self.state
-
- if state.state == STATE_UNAVAILABLE:
- return {'online': False}
-
- attrs = {'online': True}
-
- for trt in self.traits():
- deep_update(attrs, trt.query_attributes())
-
- return attrs
-
- async def execute(self, command, data, params):
- """Execute a command.
-
- https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
- """
- executed = False
- for trt in self.traits():
- if trt.can_execute(command, params):
- await trt.execute(command, data, params)
- executed = True
- break
-
- if not executed:
- raise SmartHomeError(
- ERR_FUNCTION_NOT_SUPPORTED,
- 'Unable to execute {} for {}'.format(command,
- self.state.entity_id))
-
- @callback
- def async_update(self):
- """Update the entity with latest info from Home Assistant."""
- self.state = self.hass.states.get(self.entity_id)
-
- if self._traits is None:
- return
-
- for trt in self._traits:
- trt.state = self.state
-
async def async_handle_message(hass, config, user_id, message):
"""Handle incoming API messages."""
@@ -287,7 +89,7 @@ async def async_devices_sync(hass, data, payload):
if not data.config.should_expose(state):
continue
- entity = _GoogleEntity(hass, data.config, state)
+ entity = GoogleEntity(hass, data.config, state)
serialized = await entity.sync_serialize()
if serialized is None:
@@ -328,7 +130,7 @@ async def async_devices_query(hass, data, payload):
devices[devid] = {'online': False}
continue
- entity = _GoogleEntity(hass, data.config, state)
+ entity = GoogleEntity(hass, data.config, state)
devices[devid] = entity.query_serialize()
return {'devices': devices}
@@ -372,17 +174,15 @@ async def handle_devices_execute(hass, data, payload):
}
continue
- entities[entity_id] = _GoogleEntity(hass, data.config, state)
+ entities[entity_id] = GoogleEntity(hass, data.config, state)
try:
- await entities[entity_id].execute(execution['command'],
- data,
- execution.get('params', {}))
+ await entities[entity_id].execute(data, execution)
except SmartHomeError as err:
results[entity_id] = {
'ids': [entity_id],
'status': 'ERROR',
- 'errorCode': err.code
+ **err.to_response()
}
final_results = list(results.values())
diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py
index de3a9530b50..bad186a4edb 100644
--- a/homeassistant/components/google_assistant/trait.py
+++ b/homeassistant/components/google_assistant/trait.py
@@ -2,6 +2,7 @@
import logging
from homeassistant.components import (
+ binary_sensor,
camera,
cover,
group,
@@ -18,6 +19,7 @@ from homeassistant.components import (
from homeassistant.components.climate import const as climate
from homeassistant.const import (
ATTR_ENTITY_ID,
+ ATTR_DEVICE_CLASS,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_LOCKED,
@@ -28,11 +30,20 @@ from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE,
ATTR_ASSUMED_STATE,
+ STATE_UNKNOWN,
)
from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.util import color as color_util, temperature as temp_util
-from .const import ERR_VALUE_OUT_OF_RANGE
-from .helpers import SmartHomeError
+from .const import (
+ ERR_VALUE_OUT_OF_RANGE,
+ ERR_NOT_SUPPORTED,
+ ERR_FUNCTION_NOT_SUPPORTED,
+ ERR_CHALLENGE_NOT_SETUP,
+ CHALLENGE_ACK_NEEDED,
+ CHALLENGE_PIN_NEEDED,
+ CHALLENGE_FAILED_PIN_NEEDED,
+)
+from .error import SmartHomeError, ChallengeNeeded
_LOGGER = logging.getLogger(__name__)
@@ -42,8 +53,7 @@ TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff'
TRAIT_DOCK = PREFIX_TRAITS + 'Dock'
TRAIT_STARTSTOP = PREFIX_TRAITS + 'StartStop'
TRAIT_BRIGHTNESS = PREFIX_TRAITS + 'Brightness'
-TRAIT_COLOR_SPECTRUM = PREFIX_TRAITS + 'ColorSpectrum'
-TRAIT_COLOR_TEMP = PREFIX_TRAITS + 'ColorTemperature'
+TRAIT_COLOR_SETTING = PREFIX_TRAITS + 'ColorSetting'
TRAIT_SCENE = PREFIX_TRAITS + 'Scene'
TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + 'TemperatureSetting'
TRAIT_LOCKUNLOCK = PREFIX_TRAITS + 'LockUnlock'
@@ -109,7 +119,7 @@ class _Trait:
"""Test if command can be executed."""
return command in self.commands
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a trait command."""
raise NotImplementedError
@@ -127,7 +137,7 @@ class BrightnessTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
if domain == light.DOMAIN:
return features & light.SUPPORT_BRIGHTNESS
@@ -159,7 +169,7 @@ class BrightnessTrait(_Trait):
return response
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a brightness command."""
domain = self.state.domain
@@ -193,7 +203,7 @@ class CameraStreamTrait(_Trait):
stream_info = None
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
if domain == camera.DOMAIN:
return features & camera.SUPPORT_STREAM
@@ -214,7 +224,7 @@ class CameraStreamTrait(_Trait):
"""Return camera stream attributes."""
return self.stream_info or {}
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a get camera stream command."""
url = await self.hass.components.camera.async_request_stream(
self.state.entity_id, 'hls')
@@ -236,7 +246,7 @@ class OnOffTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
return domain in (
group.DOMAIN,
@@ -255,7 +265,7 @@ class OnOffTrait(_Trait):
"""Return OnOff query attributes."""
return {'on': self.state.state != STATE_OFF}
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute an OnOff command."""
domain = self.state.domain
@@ -273,132 +283,124 @@ class OnOffTrait(_Trait):
@register_trait
-class ColorSpectrumTrait(_Trait):
- """Trait to offer color spectrum functionality.
-
- https://developers.google.com/actions/smarthome/traits/colorspectrum
- """
-
- name = TRAIT_COLOR_SPECTRUM
- commands = [
- COMMAND_COLOR_ABSOLUTE
- ]
-
- @staticmethod
- def supported(domain, features):
- """Test if state is supported."""
- if domain != light.DOMAIN:
- return False
-
- return features & light.SUPPORT_COLOR
-
- def sync_attributes(self):
- """Return color spectrum attributes for a sync request."""
- # Other colorModel is hsv
- return {'colorModel': 'rgb'}
-
- def query_attributes(self):
- """Return color spectrum query attributes."""
- response = {}
-
- color_hs = self.state.attributes.get(light.ATTR_HS_COLOR)
- if color_hs is not None:
- response['color'] = {
- 'spectrumRGB': int(color_util.color_rgb_to_hex(
- *color_util.color_hs_to_RGB(*color_hs)), 16),
- }
-
- return response
-
- def can_execute(self, command, params):
- """Test if command can be executed."""
- return (command in self.commands and
- 'spectrumRGB' in params.get('color', {}))
-
- async def execute(self, command, data, params):
- """Execute a color spectrum command."""
- # Convert integer to hex format and left pad with 0's till length 6
- hex_value = "{0:06x}".format(params['color']['spectrumRGB'])
- color = color_util.color_RGB_to_hs(
- *color_util.rgb_hex_to_rgb_list(hex_value))
-
- await self.hass.services.async_call(light.DOMAIN, SERVICE_TURN_ON, {
- ATTR_ENTITY_ID: self.state.entity_id,
- light.ATTR_HS_COLOR: color
- }, blocking=True, context=data.context)
-
-
-@register_trait
-class ColorTemperatureTrait(_Trait):
+class ColorSettingTrait(_Trait):
"""Trait to offer color temperature functionality.
https://developers.google.com/actions/smarthome/traits/colortemperature
"""
- name = TRAIT_COLOR_TEMP
+ name = TRAIT_COLOR_SETTING
commands = [
COMMAND_COLOR_ABSOLUTE
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
if domain != light.DOMAIN:
return False
- return features & light.SUPPORT_COLOR_TEMP
+ return (features & light.SUPPORT_COLOR_TEMP or
+ features & light.SUPPORT_COLOR)
def sync_attributes(self):
"""Return color temperature attributes for a sync request."""
attrs = self.state.attributes
- # Max Kelvin is Min Mireds K = 1000000 / mireds
- # Min Kevin is Max Mireds K = 1000000 / mireds
- return {
- 'temperatureMaxK': color_util.color_temperature_mired_to_kelvin(
- attrs.get(light.ATTR_MIN_MIREDS)),
- 'temperatureMinK': color_util.color_temperature_mired_to_kelvin(
- attrs.get(light.ATTR_MAX_MIREDS)),
- }
-
- def query_attributes(self):
- """Return color temperature query attributes."""
+ features = attrs.get(ATTR_SUPPORTED_FEATURES, 0)
response = {}
- temp = self.state.attributes.get(light.ATTR_COLOR_TEMP)
- # Some faulty integrations might put 0 in here, raising exception.
- if temp == 0:
- _LOGGER.warning('Entity %s has incorrect color temperature %s',
- self.state.entity_id, temp)
- elif temp is not None:
- response['color'] = {
- 'temperature':
- color_util.color_temperature_mired_to_kelvin(temp)
+ if features & light.SUPPORT_COLOR:
+ response['colorModel'] = 'hsv'
+
+ if features & light.SUPPORT_COLOR_TEMP:
+ # Max Kelvin is Min Mireds K = 1000000 / mireds
+ # Min Kevin is Max Mireds K = 1000000 / mireds
+ response['colorTemperatureRange'] = {
+ 'temperatureMaxK':
+ color_util.color_temperature_mired_to_kelvin(
+ attrs.get(light.ATTR_MIN_MIREDS)),
+ 'temperatureMinK':
+ color_util.color_temperature_mired_to_kelvin(
+ attrs.get(light.ATTR_MAX_MIREDS)),
}
return response
- def can_execute(self, command, params):
- """Test if command can be executed."""
- return (command in self.commands and
- 'temperature' in params.get('color', {}))
+ def query_attributes(self):
+ """Return color temperature query attributes."""
+ features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
+ color = {}
- async def execute(self, command, data, params):
+ if features & light.SUPPORT_COLOR:
+ color_hs = self.state.attributes.get(light.ATTR_HS_COLOR)
+ brightness = self.state.attributes.get(light.ATTR_BRIGHTNESS, 1)
+ if color_hs is not None:
+ color['spectrumHsv'] = {
+ 'hue': color_hs[0],
+ 'saturation': color_hs[1]/100,
+ 'value': brightness/255,
+ }
+
+ if features & light.SUPPORT_COLOR_TEMP:
+ temp = self.state.attributes.get(light.ATTR_COLOR_TEMP)
+ # Some faulty integrations might put 0 in here, raising exception.
+ if temp == 0:
+ _LOGGER.warning('Entity %s has incorrect color temperature %s',
+ self.state.entity_id, temp)
+ elif temp is not None:
+ color['temperatureK'] = \
+ color_util.color_temperature_mired_to_kelvin(temp)
+
+ response = {}
+
+ if color:
+ response['color'] = color
+
+ return response
+
+ async def execute(self, command, data, params, challenge):
"""Execute a color temperature command."""
- temp = color_util.color_temperature_kelvin_to_mired(
- params['color']['temperature'])
- min_temp = self.state.attributes[light.ATTR_MIN_MIREDS]
- max_temp = self.state.attributes[light.ATTR_MAX_MIREDS]
+ if 'temperature' in params['color']:
+ temp = color_util.color_temperature_kelvin_to_mired(
+ params['color']['temperature'])
+ min_temp = self.state.attributes[light.ATTR_MIN_MIREDS]
+ max_temp = self.state.attributes[light.ATTR_MAX_MIREDS]
- if temp < min_temp or temp > max_temp:
- raise SmartHomeError(
- ERR_VALUE_OUT_OF_RANGE,
- "Temperature should be between {} and {}".format(min_temp,
- max_temp))
+ if temp < min_temp or temp > max_temp:
+ raise SmartHomeError(
+ ERR_VALUE_OUT_OF_RANGE,
+ "Temperature should be between {} and {}".format(min_temp,
+ max_temp))
- await self.hass.services.async_call(light.DOMAIN, SERVICE_TURN_ON, {
- ATTR_ENTITY_ID: self.state.entity_id,
- light.ATTR_COLOR_TEMP: temp,
- }, blocking=True, context=data.context)
+ await self.hass.services.async_call(
+ light.DOMAIN, SERVICE_TURN_ON, {
+ ATTR_ENTITY_ID: self.state.entity_id,
+ light.ATTR_COLOR_TEMP: temp,
+ }, blocking=True, context=data.context)
+
+ elif 'spectrumRGB' in params['color']:
+ # Convert integer to hex format and left pad with 0's till length 6
+ hex_value = "{0:06x}".format(params['color']['spectrumRGB'])
+ color = color_util.color_RGB_to_hs(
+ *color_util.rgb_hex_to_rgb_list(hex_value))
+
+ await self.hass.services.async_call(
+ light.DOMAIN, SERVICE_TURN_ON, {
+ ATTR_ENTITY_ID: self.state.entity_id,
+ light.ATTR_HS_COLOR: color
+ }, blocking=True, context=data.context)
+
+ elif 'spectrumHSV' in params['color']:
+ color = params['color']['spectrumHSV']
+ saturation = color['saturation'] * 100
+ brightness = color['value'] * 255
+
+ await self.hass.services.async_call(
+ light.DOMAIN, SERVICE_TURN_ON, {
+ ATTR_ENTITY_ID: self.state.entity_id,
+ light.ATTR_HS_COLOR: [color['hue'], saturation],
+ light.ATTR_BRIGHTNESS: brightness
+ }, blocking=True, context=data.context)
@register_trait
@@ -414,7 +416,7 @@ class SceneTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
return domain in (scene.DOMAIN, script.DOMAIN)
@@ -427,7 +429,7 @@ class SceneTrait(_Trait):
"""Return scene query attributes."""
return {}
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a scene command."""
# Don't block for scripts as they can be slow.
await self.hass.services.async_call(
@@ -450,7 +452,7 @@ class DockTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
return domain == vacuum.DOMAIN
@@ -462,7 +464,7 @@ class DockTrait(_Trait):
"""Return dock query attributes."""
return {'isDocked': self.state.state == vacuum.STATE_DOCKED}
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a dock command."""
await self.hass.services.async_call(
self.state.domain, vacuum.SERVICE_RETURN_TO_BASE, {
@@ -484,7 +486,7 @@ class StartStopTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
return domain == vacuum.DOMAIN
@@ -501,7 +503,7 @@ class StartStopTrait(_Trait):
'isPaused': self.state.state == vacuum.STATE_PAUSED,
}
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a StartStop command."""
if command == COMMAND_STARTSTOP:
if params['start']:
@@ -554,7 +556,7 @@ class TemperatureSettingTrait(_Trait):
google_to_hass = {value: key for key, value in hass_to_google.items()}
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
if domain != climate.DOMAIN:
return False
@@ -637,7 +639,7 @@ class TemperatureSettingTrait(_Trait):
return response
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute a temperature point or mode command."""
# All sent in temperatures are always in Celsius
unit = self.hass.config.units.temperature_unit
@@ -739,7 +741,7 @@ class LockUnlockTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
return domain == lock.DOMAIN
@@ -751,13 +753,10 @@ class LockUnlockTrait(_Trait):
"""Return LockUnlock query attributes."""
return {'isLocked': self.state.state == STATE_LOCKED}
- def can_execute(self, command, params):
- """Test if command can be executed."""
- allowed_unlock = not params['lock'] and self.config.allow_unlock
- return params['lock'] or allowed_unlock
-
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute an LockUnlock command."""
+ _verify_pin_challenge(data, challenge)
+
if params['lock']:
service = lock.SERVICE_LOCK
else:
@@ -790,7 +789,7 @@ class FanSpeedTrait(_Trait):
}
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
if domain != fan.DOMAIN:
return False
@@ -835,7 +834,7 @@ class FanSpeedTrait(_Trait):
return response
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute an SetFanSpeed command."""
await self.hass.services.async_call(
fan.DOMAIN, fan.SERVICE_SET_SPEED, {
@@ -941,7 +940,7 @@ class ModesTrait(_Trait):
}
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
if domain != media_player.DOMAIN:
return False
@@ -1009,7 +1008,7 @@ class ModesTrait(_Trait):
return response
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute an SetModes command."""
settings = params.get('updateModeSettings')
requested_source = settings.get(
@@ -1042,13 +1041,25 @@ class OpenCloseTrait(_Trait):
]
@staticmethod
- def supported(domain, features):
+ def supported(domain, features, device_class):
"""Test if state is supported."""
- return domain == cover.DOMAIN
+ if domain == cover.DOMAIN:
+ return True
+
+ return domain == binary_sensor.DOMAIN and device_class in (
+ binary_sensor.DEVICE_CLASS_DOOR,
+ binary_sensor.DEVICE_CLASS_GARAGE_DOOR,
+ binary_sensor.DEVICE_CLASS_LOCK,
+ binary_sensor.DEVICE_CLASS_OPENING,
+ binary_sensor.DEVICE_CLASS_WINDOW,
+ )
def sync_attributes(self):
"""Return opening direction."""
- return {}
+ attrs = {}
+ if self.state.domain == binary_sensor.DOMAIN:
+ attrs['queryOnlyOpenClose'] = True
+ return attrs
def query_attributes(self):
"""Return state query attributes."""
@@ -1060,43 +1071,83 @@ class OpenCloseTrait(_Trait):
# Google will not issue an open command if the assumed state is
# open, even if that is currently incorrect.
if self.state.attributes.get(ATTR_ASSUMED_STATE):
- response['openPercent'] = 50
- else:
- position = self.state.attributes.get(
- cover.ATTR_CURRENT_POSITION
- )
+ raise SmartHomeError(
+ ERR_NOT_SUPPORTED,
+ 'Querying state is not supported')
- if position is not None:
- response['openPercent'] = position
- elif self.state.state != cover.STATE_CLOSED:
- response['openPercent'] = 100
- else:
- response['openPercent'] = 0
+ if self.state.state == STATE_UNKNOWN:
+ raise SmartHomeError(
+ ERR_NOT_SUPPORTED,
+ 'Querying state is not supported')
+
+ position = self.state.attributes.get(
+ cover.ATTR_CURRENT_POSITION
+ )
+
+ if position is not None:
+ response['openPercent'] = position
+ elif self.state.state != cover.STATE_CLOSED:
+ response['openPercent'] = 100
+ else:
+ response['openPercent'] = 0
+
+ elif domain == binary_sensor.DOMAIN:
+ if self.state.state == STATE_ON:
+ response['openPercent'] = 100
+ else:
+ response['openPercent'] = 0
return response
- async def execute(self, command, data, params):
+ async def execute(self, command, data, params, challenge):
"""Execute an Open, close, Set position command."""
domain = self.state.domain
if domain == cover.DOMAIN:
+ if self.state.attributes.get(ATTR_DEVICE_CLASS) in (
+ cover.DEVICE_CLASS_DOOR, cover.DEVICE_CLASS_GARAGE
+ ):
+ _verify_pin_challenge(data, challenge)
+
position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION)
- if position is not None:
+ if params['openPercent'] == 0:
+ await self.hass.services.async_call(
+ cover.DOMAIN, cover.SERVICE_CLOSE_COVER, {
+ ATTR_ENTITY_ID: self.state.entity_id
+ }, blocking=True, context=data.context)
+ elif params['openPercent'] == 100:
+ await self.hass.services.async_call(
+ cover.DOMAIN, cover.SERVICE_OPEN_COVER, {
+ ATTR_ENTITY_ID: self.state.entity_id
+ }, blocking=True, context=data.context)
+ elif position is not None:
await self.hass.services.async_call(
cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION, {
ATTR_ENTITY_ID: self.state.entity_id,
cover.ATTR_POSITION: params['openPercent']
}, blocking=True, context=data.context)
else:
- if self.state.state != cover.STATE_CLOSED:
- if params['openPercent'] < 100:
- await self.hass.services.async_call(
- cover.DOMAIN, cover.SERVICE_CLOSE_COVER, {
- ATTR_ENTITY_ID: self.state.entity_id
- }, blocking=True, context=data.context)
- else:
- if params['openPercent'] > 0:
- await self.hass.services.async_call(
- cover.DOMAIN, cover.SERVICE_OPEN_COVER, {
- ATTR_ENTITY_ID: self.state.entity_id
- }, blocking=True, context=data.context)
+ raise SmartHomeError(
+ ERR_FUNCTION_NOT_SUPPORTED,
+ 'Setting a position is not supported')
+
+
+def _verify_pin_challenge(data, challenge):
+ """Verify a pin challenge."""
+ if not data.config.secure_devices_pin:
+ raise SmartHomeError(
+ ERR_CHALLENGE_NOT_SETUP, 'Challenge is not set up')
+
+ if not challenge:
+ raise ChallengeNeeded(CHALLENGE_PIN_NEEDED)
+
+ pin = challenge.get('pin')
+
+ if pin != data.config.secure_devices_pin:
+ raise ChallengeNeeded(CHALLENGE_FAILED_PIN_NEEDED)
+
+
+def _verify_ack_challenge(data, challenge):
+ """Verify a pin challenge."""
+ if not challenge or not challenge.get('ack'):
+ raise ChallengeNeeded(CHALLENGE_ACK_NEEDED)
diff --git a/homeassistant/components/google_domains/manifest.json b/homeassistant/components/google_domains/manifest.json
new file mode 100644
index 00000000000..190e5860ee6
--- /dev/null
+++ b/homeassistant/components/google_domains/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "google_domains",
+ "name": "Google domains",
+ "documentation": "https://www.home-assistant.io/components/google_domains",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google_maps/device_tracker.py b/homeassistant/components/google_maps/device_tracker.py
index 3de60d6cb38..5788392190a 100644
--- a/homeassistant/components/google_maps/device_tracker.py
+++ b/homeassistant/components/google_maps/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Google Maps location sharing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.google_maps/
-"""
+"""Support for Google Maps location sharing."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify, dt as dt_util
-REQUIREMENTS = ['locationsharinglib==3.0.11']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
diff --git a/homeassistant/components/google_maps/manifest.json b/homeassistant/components/google_maps/manifest.json
new file mode 100644
index 00000000000..7d6aeeef041
--- /dev/null
+++ b/homeassistant/components/google_maps/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "google_maps",
+ "name": "Google maps",
+ "documentation": "https://www.home-assistant.io/components/google_maps",
+ "requirements": [
+ "locationsharinglib==3.0.11"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py
index 18c068ea454..8aaa7a17ac4 100644
--- a/homeassistant/components/google_pubsub/__init__.py
+++ b/homeassistant/components/google_pubsub/__init__.py
@@ -15,8 +15,6 @@ from homeassistant.helpers.entityfilter import FILTER_SCHEMA
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['google-cloud-pubsub==0.39.1']
-
DOMAIN = 'google_pubsub'
CONF_PROJECT_ID = 'project_id'
diff --git a/homeassistant/components/google_pubsub/manifest.json b/homeassistant/components/google_pubsub/manifest.json
new file mode 100644
index 00000000000..ff61ad0e05d
--- /dev/null
+++ b/homeassistant/components/google_pubsub/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "google_pubsub",
+ "name": "Google pubsub",
+ "documentation": "https://www.home-assistant.io/components/google_pubsub",
+ "requirements": [
+ "google-cloud-pubsub==0.39.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google_translate/__init__.py b/homeassistant/components/google_translate/__init__.py
new file mode 100644
index 00000000000..f7860c57d99
--- /dev/null
+++ b/homeassistant/components/google_translate/__init__.py
@@ -0,0 +1 @@
+"""The google_translate component."""
diff --git a/homeassistant/components/google_translate/manifest.json b/homeassistant/components/google_translate/manifest.json
new file mode 100644
index 00000000000..cb3cd350c04
--- /dev/null
+++ b/homeassistant/components/google_translate/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "google_translate",
+ "name": "Google Translate",
+ "documentation": "https://www.home-assistant.io/components/google_translate",
+ "requirements": [
+ "gTTS-token==1.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@awarecan"
+ ]
+}
diff --git a/homeassistant/components/google/tts.py b/homeassistant/components/google_translate/tts.py
similarity index 99%
rename from homeassistant/components/google/tts.py
rename to homeassistant/components/google_translate/tts.py
index 49a945cbbfd..4d988bed21c 100644
--- a/homeassistant/components/google/tts.py
+++ b/homeassistant/components/google_translate/tts.py
@@ -12,8 +12,6 @@ import yarl
from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['gTTS-token==1.1.3']
-
_LOGGER = logging.getLogger(__name__)
GOOGLE_SPEECH_URL = "https://translate.google.com/translate_tts"
diff --git a/homeassistant/components/google_travel_time/manifest.json b/homeassistant/components/google_travel_time/manifest.json
new file mode 100644
index 00000000000..eaa168332a6
--- /dev/null
+++ b/homeassistant/components/google_travel_time/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "google_travel_time",
+ "name": "Google travel time",
+ "documentation": "https://www.home-assistant.io/components/google_travel_time",
+ "requirements": [
+ "googlemaps==2.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py
index 86b1a7aff44..ef4fc76f53e 100644
--- a/homeassistant/components/google_travel_time/sensor.py
+++ b/homeassistant/components/google_travel_time/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Google travel time sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.google_travel_time/
-"""
+"""Support for Google travel time sensors."""
import logging
from datetime import datetime
from datetime import timedelta
@@ -20,8 +15,6 @@ from homeassistant.helpers import location
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['googlemaps==2.5.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_DESTINATION = 'destination'
diff --git a/homeassistant/components/google_wifi/manifest.json b/homeassistant/components/google_wifi/manifest.json
new file mode 100644
index 00000000000..6e840458207
--- /dev/null
+++ b/homeassistant/components/google_wifi/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "google_wifi",
+ "name": "Google wifi",
+ "documentation": "https://www.home-assistant.io/components/google_wifi",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py
index b78e9afb8b9..202e2a0eb46 100644
--- a/homeassistant/components/google_wifi/sensor.py
+++ b/homeassistant/components/google_wifi/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for retrieving status info from Google Wifi/OnHub routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.google_wifi/
-"""
+"""Support for retrieving status info from Google Wifi/OnHub routers."""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/googlehome/__init__.py b/homeassistant/components/googlehome/__init__.py
index 6ebc2f512b1..073081a9634 100644
--- a/homeassistant/components/googlehome/__init__.py
+++ b/homeassistant/components/googlehome/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['googledevices==1.0.2']
-
DOMAIN = 'googlehome'
CLIENT = 'googlehome_client'
diff --git a/homeassistant/components/googlehome/device_tracker.py b/homeassistant/components/googlehome/device_tracker.py
index c024cde0c6c..3b6bc5d341c 100644
--- a/homeassistant/components/googlehome/device_tracker.py
+++ b/homeassistant/components/googlehome/device_tracker.py
@@ -10,8 +10,6 @@ from . import CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['googlehome']
-
DEFAULT_SCAN_INTERVAL = timedelta(seconds=10)
diff --git a/homeassistant/components/googlehome/manifest.json b/homeassistant/components/googlehome/manifest.json
new file mode 100644
index 00000000000..107e7d634f0
--- /dev/null
+++ b/homeassistant/components/googlehome/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "googlehome",
+ "name": "Googlehome",
+ "documentation": "https://www.home-assistant.io/components/googlehome",
+ "requirements": [
+ "googledevices==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/googlehome/sensor.py b/homeassistant/components/googlehome/sensor.py
index 4f37740da85..088f4352fa3 100644
--- a/homeassistant/components/googlehome/sensor.py
+++ b/homeassistant/components/googlehome/sensor.py
@@ -8,8 +8,6 @@ import homeassistant.util.dt as dt_util
from . import CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME
-DEPENDENCIES = ['googlehome']
-
SCAN_INTERVAL = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/gpmdp/manifest.json b/homeassistant/components/gpmdp/manifest.json
new file mode 100644
index 00000000000..98ab8035023
--- /dev/null
+++ b/homeassistant/components/gpmdp/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gpmdp",
+ "name": "Gpmdp",
+ "documentation": "https://www.home-assistant.io/components/gpmdp",
+ "requirements": [
+ "websocket-client==0.54.0"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gpmdp/media_player.py b/homeassistant/components/gpmdp/media_player.py
index c72d14ebb8a..76253d32db8 100644
--- a/homeassistant/components/gpmdp/media_player.py
+++ b/homeassistant/components/gpmdp/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Google Play Music Desktop Player.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.gpmdp/
-"""
+"""Support for Google Play Music Desktop Player."""
import json
import logging
import socket
@@ -21,8 +16,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['websocket-client==0.54.0']
-
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/gpsd/manifest.json b/homeassistant/components/gpsd/manifest.json
new file mode 100644
index 00000000000..b35d5cb1850
--- /dev/null
+++ b/homeassistant/components/gpsd/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "gpsd",
+ "name": "Gpsd",
+ "documentation": "https://www.home-assistant.io/components/gpsd",
+ "requirements": [
+ "gps3==0.33.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py
index b1ce428e1f2..cccf59a822a 100644
--- a/homeassistant/components/gpsd/sensor.py
+++ b/homeassistant/components/gpsd/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for GPSD.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.gpsd/
-"""
+"""Support for GPSD."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['gps3==0.33.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CLIMB = 'climb'
diff --git a/homeassistant/components/gpslogger/.translations/es.json b/homeassistant/components/gpslogger/.translations/es.json
index cd14e21db10..7b90a5c5caa 100644
--- a/homeassistant/components/gpslogger/.translations/es.json
+++ b/homeassistant/components/gpslogger/.translations/es.json
@@ -3,6 +3,16 @@
"abort": {
"not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.",
"one_instance_allowed": "Solo se necesita una instancia."
- }
+ },
+ "create_entry": {
+ "default": "Para enviar eventos a Home Assistant, necesitar\u00e1s configurar la funci\u00f3n de webhook en GPSLogger.\n\nRellena la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nEcha un vistazo a [la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles."
+ },
+ "step": {
+ "user": {
+ "description": "\u00bfEst\u00e1s seguro de que quieres configurar el webhook de GPSLogger?",
+ "title": "Configurar el webhook de GPSLogger"
+ }
+ },
+ "title": "Webhook de GPSLogger"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py
index 12da63d8ebb..6887b85d02d 100644
--- a/homeassistant/components/gpslogger/__init__.py
+++ b/homeassistant/components/gpslogger/__init__.py
@@ -15,8 +15,6 @@ from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'gpslogger'
-DEPENDENCIES = ['webhook']
-
TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN)
ATTR_ALTITUDE = 'altitude'
@@ -104,6 +102,11 @@ async def async_unload_entry(hass, entry):
await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER)
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'GPSLogger Webhook',
diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py
index c9496975272..67967821083 100644
--- a/homeassistant/components/gpslogger/device_tracker.py
+++ b/homeassistant/components/gpslogger/device_tracker.py
@@ -10,8 +10,6 @@ from . import DOMAIN as GPSLOGGER_DOMAIN, TRACKER_UPDATE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['gpslogger']
-
DATA_KEY = '{}.{}'.format(GPSLOGGER_DOMAIN, DEVICE_TRACKER_DOMAIN)
diff --git a/homeassistant/components/gpslogger/manifest.json b/homeassistant/components/gpslogger/manifest.json
new file mode 100644
index 00000000000..2d2166c1bb1
--- /dev/null
+++ b/homeassistant/components/gpslogger/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gpslogger",
+ "name": "Gpslogger",
+ "documentation": "https://www.home-assistant.io/components/gpslogger",
+ "requirements": [],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/graphite/manifest.json b/homeassistant/components/graphite/manifest.json
new file mode 100644
index 00000000000..a5eefc5af04
--- /dev/null
+++ b/homeassistant/components/graphite/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "graphite",
+ "name": "Graphite",
+ "documentation": "https://www.home-assistant.io/components/graphite",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/greeneye_monitor/__init__.py b/homeassistant/components/greeneye_monitor/__init__.py
index aedc98aac31..0f12c3cd479 100644
--- a/homeassistant/components/greeneye_monitor/__init__.py
+++ b/homeassistant/components/greeneye_monitor/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ['greeneye_monitor==1.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_CHANNELS = 'channels'
diff --git a/homeassistant/components/greeneye_monitor/manifest.json b/homeassistant/components/greeneye_monitor/manifest.json
new file mode 100644
index 00000000000..7bfb87ede47
--- /dev/null
+++ b/homeassistant/components/greeneye_monitor/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "greeneye_monitor",
+ "name": "Greeneye monitor",
+ "documentation": "https://www.home-assistant.io/components/greeneye_monitor",
+ "requirements": [
+ "greeneye_monitor==1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py
index 4dee9d69b42..ddfa5c1504b 100644
--- a/homeassistant/components/greeneye_monitor/sensor.py
+++ b/homeassistant/components/greeneye_monitor/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the sensors in a GreenEye Monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensors.greeneye_monitor_temperature/
-"""
+"""Support for the sensors in a GreenEye Monitor."""
import logging
from homeassistant.const import CONF_NAME, CONF_TEMPERATURE_UNIT, POWER_WATT
@@ -28,8 +23,6 @@ from ..greeneye_monitor import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['greeneye_monitor']
-
DATA_PULSES = 'pulses'
DATA_WATT_SECONDS = 'watt_seconds'
diff --git a/homeassistant/components/greenwave/light.py b/homeassistant/components/greenwave/light.py
index 0c484a0e3f4..a8418a01ac2 100644
--- a/homeassistant/components/greenwave/light.py
+++ b/homeassistant/components/greenwave/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Greenwave Reality (TCP Connected) lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.greenwave/
-"""
+"""Support for Greenwave Reality (TCP Connected) lights."""
import logging
from datetime import timedelta
@@ -15,7 +10,6 @@ from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['greenwavereality==0.5.1']
_LOGGER = logging.getLogger(__name__)
CONF_VERSION = 'version'
diff --git a/homeassistant/components/greenwave/manifest.json b/homeassistant/components/greenwave/manifest.json
new file mode 100644
index 00000000000..1032b5eaf2a
--- /dev/null
+++ b/homeassistant/components/greenwave/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "greenwave",
+ "name": "Greenwave",
+ "documentation": "https://www.home-assistant.io/components/greenwave",
+ "requirements": [
+ "greenwavereality==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py
index c1825211433..385d20949d6 100644
--- a/homeassistant/components/group/cover.py
+++ b/homeassistant/components/group/cover.py
@@ -1,9 +1,4 @@
-"""
-This platform allows several cover to be grouped into one cover.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/cover.group/
-"""
+"""This platform allows several cover to be grouped into one cover."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py
index c37c5cc4e8e..170e93398a1 100644
--- a/homeassistant/components/group/light.py
+++ b/homeassistant/components/group/light.py
@@ -1,9 +1,4 @@
-"""
-This platform allows several lights to be grouped into one light.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.group/
-"""
+"""This platform allows several lights to be grouped into one light."""
from collections import Counter
import itertools
import logging
diff --git a/homeassistant/components/group/manifest.json b/homeassistant/components/group/manifest.json
new file mode 100644
index 00000000000..aa99e20a4df
--- /dev/null
+++ b/homeassistant/components/group/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "group",
+ "name": "Group",
+ "documentation": "https://www.home-assistant.io/components/group",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/group/notify.py b/homeassistant/components/group/notify.py
index 200464bb8cb..b59c49563e2 100644
--- a/homeassistant/components/group/notify.py
+++ b/homeassistant/components/group/notify.py
@@ -1,9 +1,4 @@
-"""
-Group platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.group/
-"""
+"""Group platform for notify component."""
import asyncio
from collections.abc import Mapping
from copy import deepcopy
diff --git a/homeassistant/components/gstreamer/manifest.json b/homeassistant/components/gstreamer/manifest.json
new file mode 100644
index 00000000000..6bfb8abbe0b
--- /dev/null
+++ b/homeassistant/components/gstreamer/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gstreamer",
+ "name": "Gstreamer",
+ "documentation": "https://www.home-assistant.io/components/gstreamer",
+ "requirements": [
+ "gstreamer-player==1.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py
index c6571894472..f7404010513 100644
--- a/homeassistant/components/gstreamer/media_player.py
+++ b/homeassistant/components/gstreamer/media_player.py
@@ -1,9 +1,4 @@
-"""
-Play media via gstreamer.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.gstreamer/
-"""
+"""Play media via gstreamer."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['gstreamer-player==1.1.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_PIPELINE = 'pipeline'
diff --git a/homeassistant/components/gtfs/manifest.json b/homeassistant/components/gtfs/manifest.json
new file mode 100644
index 00000000000..1c7ddbd65ee
--- /dev/null
+++ b/homeassistant/components/gtfs/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "gtfs",
+ "name": "Gtfs",
+ "documentation": "https://www.home-assistant.io/components/gtfs",
+ "requirements": [
+ "pygtfs==0.1.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py
index 5555459aa16..0a9301f8c33 100644
--- a/homeassistant/components/gtfs/sensor.py
+++ b/homeassistant/components/gtfs/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for GTFS (Google/General Transport Format Schema).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.gtfs/
-"""
+"""Support for GTFS (Google/General Transport Format Schema)."""
import datetime
import logging
import os
@@ -22,8 +17,6 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util import slugify
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pygtfs==0.1.5']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ARRIVAL = 'arrival'
@@ -130,7 +123,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # type: ignore
def get_next_departure(schedule: Any, start_station_id: Any,
end_station_id: Any, offset: cv.time_period,
- include_tomorrow: cv.boolean = False) -> dict:
+ include_tomorrow: bool = False) -> dict:
"""Get the next departure for the given schedule."""
now = datetime.datetime.now() + offset
now_date = now.strftime(dt_util.DATE_STR_FORMAT)
@@ -147,7 +140,7 @@ def get_next_departure(schedule: Any, start_station_id: Any,
limit = 24 * 60 * 60 * 2
tomorrow_select = tomorrow_where = tomorrow_order = ''
if include_tomorrow:
- limit = limit / 2 * 3
+ limit = int(limit / 2 * 3)
tomorrow_name = tomorrow.strftime('%A').lower()
tomorrow_select = "calendar.{} AS tomorrow,".format(tomorrow_name)
tomorrow_where = "OR calendar.{} = 1".format(tomorrow_name)
@@ -218,7 +211,7 @@ def get_next_departure(schedule: Any, start_station_id: Any,
# as long as all departures are within the calendar date range.
timetable = {}
yesterday_start = today_start = tomorrow_start = None
- yesterday_last = today_last = None
+ yesterday_last = today_last = ''
for row in result:
if row['yesterday'] == 1 and yesterday_date >= row['start_date']:
@@ -268,13 +261,12 @@ def get_next_departure(schedule: Any, start_station_id: Any,
timetable[idx] = {**row, **extras}
# Flag last departures.
- for idx in [yesterday_last, today_last]:
- if idx is not None:
- timetable[idx]['last'] = True
+ for idx in filter(None, [yesterday_last, today_last]):
+ timetable[idx]['last'] = True
_LOGGER.debug("Timetable: %s", sorted(timetable.keys()))
- item = {}
+ item = {} # type: dict
for key in sorted(timetable.keys()):
if dt_util.parse_datetime(key) > now:
item = timetable[key]
@@ -350,22 +342,22 @@ def get_next_departure(schedule: Any, start_station_id: Any,
def setup_platform(hass: HomeAssistantType, config: ConfigType,
add_entities: Callable[[list], None],
- discovery_info: Optional[dict] = None) -> bool:
+ discovery_info: Optional[dict] = None) -> None:
"""Set up the GTFS sensor."""
gtfs_dir = hass.config.path(DEFAULT_PATH)
- data = str(config.get(CONF_DATA))
+ data = config[CONF_DATA]
origin = config.get(CONF_ORIGIN)
destination = config.get(CONF_DESTINATION)
name = config.get(CONF_NAME)
offset = config.get(CONF_OFFSET)
- include_tomorrow = config.get(CONF_TOMORROW)
+ include_tomorrow = config[CONF_TOMORROW]
if not os.path.exists(gtfs_dir):
os.makedirs(gtfs_dir)
if not os.path.exists(os.path.join(gtfs_dir, data)):
_LOGGER.error("The given GTFS data file/folder was not found")
- return False
+ return
import pygtfs
@@ -382,7 +374,6 @@ def setup_platform(hass: HomeAssistantType, config: ConfigType,
add_entities([
GTFSDepartureSensor(gtfs, name, origin, destination, offset,
include_tomorrow)])
- return True
class GTFSDepartureSensor(Entity):
@@ -390,7 +381,7 @@ class GTFSDepartureSensor(Entity):
def __init__(self, pygtfs: Any, name: Optional[Any], origin: Any,
destination: Any, offset: cv.time_period,
- include_tomorrow: cv.boolean) -> None:
+ include_tomorrow: bool) -> None:
"""Initialize the sensor."""
self._pygtfs = pygtfs
self.origin = origin
@@ -402,7 +393,7 @@ class GTFSDepartureSensor(Entity):
self._available = False
self._icon = ICON
self._name = ''
- self._state = None
+ self._state = None # type: Optional[str]
self._attributes = {} # type: dict
self._agency = None
@@ -421,10 +412,8 @@ class GTFSDepartureSensor(Entity):
return self._name
@property
- def state(self) -> str:
+ def state(self) -> Optional[str]: # type: ignore
"""Return the state of the sensor."""
- if self._state is None:
- return STATE_UNKNOWN
return self._state
@property
@@ -488,26 +477,27 @@ class GTFSDepartureSensor(Entity):
else:
trip_id = self._departure['trip_id']
if not self._trip or self._trip.trip_id != trip_id:
- _LOGGER.info("Fetching trip details for %s", trip_id)
+ _LOGGER.debug("Fetching trip details for %s", trip_id)
self._trip = self._pygtfs.trips_by_id(trip_id)[0]
route_id = self._departure['route_id']
if not self._route or self._route.route_id != route_id:
- _LOGGER.info("Fetching route details for %s", route_id)
+ _LOGGER.debug("Fetching route details for %s", route_id)
self._route = self._pygtfs.routes_by_id(route_id)[0]
# Fetch agency details exactly once
if self._agency is None and self._route:
+ _LOGGER.debug("Fetching agency details for %s",
+ self._route.agency_id)
try:
- _LOGGER.info("Fetching agency details for %s",
- self._route.agency_id)
self._agency = self._pygtfs.agencies_by_id(
self._route.agency_id)[0]
except IndexError:
_LOGGER.warning(
- "Agency ID '%s' not found in agency table. You may "
- "want to update the agency database table to fix this "
- "missing reference.", self._route.agency_id)
+ "Agency ID '%s' was not found in agency table, "
+ "you may want to update the routes database table "
+ "to fix this missing reference",
+ self._route.agency_id)
self._agency = False
# Assign attributes, icon and name
@@ -540,21 +530,21 @@ class GTFSDepartureSensor(Entity):
if self._departure[ATTR_FIRST] is not None:
self._attributes[ATTR_FIRST] = self._departure['first']
- elif ATTR_FIRST in self._attributes.keys():
+ elif ATTR_FIRST in self._attributes:
del self._attributes[ATTR_FIRST]
if self._departure[ATTR_LAST] is not None:
self._attributes[ATTR_LAST] = self._departure['last']
- elif ATTR_LAST in self._attributes.keys():
+ elif ATTR_LAST in self._attributes:
del self._attributes[ATTR_LAST]
else:
- if ATTR_ARRIVAL in self._attributes.keys():
+ if ATTR_ARRIVAL in self._attributes:
del self._attributes[ATTR_ARRIVAL]
- if ATTR_DAY in self._attributes.keys():
+ if ATTR_DAY in self._attributes:
del self._attributes[ATTR_DAY]
- if ATTR_FIRST in self._attributes.keys():
+ if ATTR_FIRST in self._attributes:
del self._attributes[ATTR_FIRST]
- if ATTR_LAST in self._attributes.keys():
+ if ATTR_LAST in self._attributes:
del self._attributes[ATTR_LAST]
# Add contextual information
@@ -563,21 +553,21 @@ class GTFSDepartureSensor(Entity):
if self._state is None:
self._attributes[ATTR_INFO] = "No more departures" if \
self._include_tomorrow else "No more departures today"
- elif ATTR_INFO in self._attributes.keys():
+ elif ATTR_INFO in self._attributes:
del self._attributes[ATTR_INFO]
if self._agency:
self._attributes[ATTR_ATTRIBUTION] = self._agency.agency_name
- elif ATTR_ATTRIBUTION in self._attributes.keys():
+ elif ATTR_ATTRIBUTION in self._attributes:
del self._attributes[ATTR_ATTRIBUTION]
# Add extra metadata
key = 'agency_id'
- if self._agency and key not in self._attributes.keys():
+ if self._agency and key not in self._attributes:
self.append_keys(self.dict_for_table(self._agency), 'Agency')
key = 'origin_station_stop_id'
- if self._origin and key not in self._attributes.keys():
+ if self._origin and key not in self._attributes:
self.append_keys(self.dict_for_table(self._origin),
"Origin Station")
self._attributes[ATTR_LOCATION_ORIGIN] = \
@@ -590,7 +580,7 @@ class GTFSDepartureSensor(Entity):
WHEELCHAIR_BOARDING_DEFAULT)
key = 'destination_station_stop_id'
- if self._destination and key not in self._attributes.keys():
+ if self._destination and key not in self._attributes:
self.append_keys(self.dict_for_table(self._destination),
"Destination Station")
self._attributes[ATTR_LOCATION_DESTINATION] = \
@@ -604,9 +594,9 @@ class GTFSDepartureSensor(Entity):
# Manage Route metadata
key = 'route_id'
- if not self._route and key in self._attributes.keys():
+ if not self._route and key in self._attributes:
self.remove_keys('Route')
- elif self._route and (key not in self._attributes.keys() or
+ elif self._route and (key not in self._attributes or
self._attributes[key] != self._route.route_id):
self.append_keys(self.dict_for_table(self._route), 'Route')
self._attributes[ATTR_ROUTE_TYPE] = \
@@ -614,9 +604,9 @@ class GTFSDepartureSensor(Entity):
# Manage Trip metadata
key = 'trip_id'
- if not self._trip and key in self._attributes.keys():
+ if not self._trip and key in self._attributes:
self.remove_keys('Trip')
- elif self._trip and (key not in self._attributes.keys() or
+ elif self._trip and (key not in self._attributes or
self._attributes[key] != self._trip.trip_id):
self.append_keys(self.dict_for_table(self._trip), 'Trip')
self._attributes[ATTR_BICYCLE] = BICYCLE_ALLOWED_OPTIONS.get(
diff --git a/homeassistant/components/gtt/manifest.json b/homeassistant/components/gtt/manifest.json
new file mode 100644
index 00000000000..142261fe155
--- /dev/null
+++ b/homeassistant/components/gtt/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "gtt",
+ "name": "Gtt",
+ "documentation": "https://www.home-assistant.io/components/gtt",
+ "requirements": [
+ "pygtt==1.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/gtt/sensor.py b/homeassistant/components/gtt/sensor.py
index a64c743381d..ecabd5f0a71 100644
--- a/homeassistant/components/gtt/sensor.py
+++ b/homeassistant/components/gtt/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor to get GTT's timetable for a stop.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.gtt/
-"""
+"""Sensor to get GTT's timetable for a stop."""
import logging
from datetime import timedelta, datetime
@@ -14,8 +9,6 @@ from homeassistant.const import DEVICE_CLASS_TIMESTAMP
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pygtt==1.1.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_STOP = 'stop'
diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py
index 23113a1388b..611e8df006a 100644
--- a/homeassistant/components/habitica/__init__.py
+++ b/homeassistant/components/habitica/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['habitipy==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_API_USER = 'api_user'
diff --git a/homeassistant/components/habitica/manifest.json b/homeassistant/components/habitica/manifest.json
new file mode 100644
index 00000000000..b8e622823d3
--- /dev/null
+++ b/homeassistant/components/habitica/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "habitica",
+ "name": "Habitica",
+ "documentation": "https://www.home-assistant.io/components/habitica",
+ "requirements": [
+ "habitipy==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hangouts/.translations/ca.json b/homeassistant/components/hangouts/.translations/ca.json
index ca15e59ec19..ea43c804f2d 100644
--- a/homeassistant/components/hangouts/.translations/ca.json
+++ b/homeassistant/components/hangouts/.translations/ca.json
@@ -18,6 +18,7 @@
},
"user": {
"data": {
+ "authorization_code": "Codi d'autoritzaci\u00f3 (necessari per a l'autenticaci\u00f3 manual)",
"email": "Correu electr\u00f2nic",
"password": "Contrasenya"
},
diff --git a/homeassistant/components/hangouts/.translations/de.json b/homeassistant/components/hangouts/.translations/de.json
index c8e84983fb6..fa96c00f666 100644
--- a/homeassistant/components/hangouts/.translations/de.json
+++ b/homeassistant/components/hangouts/.translations/de.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "Autorisierungscode (f\u00fcr die manuelle Authentifizierung erforderlich)",
"email": "E-Mail-Adresse",
"password": "Passwort"
},
diff --git a/homeassistant/components/hangouts/.translations/en.json b/homeassistant/components/hangouts/.translations/en.json
index f526bec4f34..31e5f9894f9 100644
--- a/homeassistant/components/hangouts/.translations/en.json
+++ b/homeassistant/components/hangouts/.translations/en.json
@@ -18,6 +18,7 @@
},
"user": {
"data": {
+ "authorization_code": "Authorization Code (required for manual authentication)",
"email": "E-Mail Address",
"password": "Password"
},
diff --git a/homeassistant/components/hangouts/.translations/es-419.json b/homeassistant/components/hangouts/.translations/es-419.json
index ab78213b53a..951a30f1826 100644
--- a/homeassistant/components/hangouts/.translations/es-419.json
+++ b/homeassistant/components/hangouts/.translations/es-419.json
@@ -13,6 +13,7 @@
},
"user": {
"data": {
+ "authorization_code": "C\u00f3digo de autorizaci\u00f3n (requerido para la autenticaci\u00f3n manual)",
"email": "Direcci\u00f3n de correo electr\u00f3nico",
"password": "Contrase\u00f1a"
},
diff --git a/homeassistant/components/hangouts/.translations/es.json b/homeassistant/components/hangouts/.translations/es.json
index 4b7ad390ceb..01200a3aef9 100644
--- a/homeassistant/components/hangouts/.translations/es.json
+++ b/homeassistant/components/hangouts/.translations/es.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "C\u00f3digo de autorizaci\u00f3n (requerido para la autenticaci\u00f3n manual)",
"email": "Correo electr\u00f3nico",
"password": "Contrase\u00f1a"
},
diff --git a/homeassistant/components/hangouts/.translations/ko.json b/homeassistant/components/hangouts/.translations/ko.json
index b1bcf5725be..e045f3359d1 100644
--- a/homeassistant/components/hangouts/.translations/ko.json
+++ b/homeassistant/components/hangouts/.translations/ko.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "\uc778\uc99d \ucf54\ub4dc (\uc218\ub3d9 \uc778\uc99d\uc5d0 \ud544\uc694)",
"email": "\uc774\uba54\uc77c \uc8fc\uc18c",
"password": "\ube44\ubc00\ubc88\ud638"
},
diff --git a/homeassistant/components/hangouts/.translations/lb.json b/homeassistant/components/hangouts/.translations/lb.json
index 426ab689626..c22b02fd7ed 100644
--- a/homeassistant/components/hangouts/.translations/lb.json
+++ b/homeassistant/components/hangouts/.translations/lb.json
@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "Google Hangouts ass scho konfigur\u00e9iert",
- "unknown": "Onbekannten Fehler opgetrueden"
+ "unknown": "Onbekannten Feeler opgetrueden"
},
"error": {
"invalid_2fa": "Ong\u00eblteg 2-Faktor Authentifikatioun, prob\u00e9iert w.e.g. nach emol.",
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "Autorisatioun's Code (n\u00e9ideg fir eng manuell Authentifikatioun)",
"email": "E-Mail Adress",
"password": "Passwuert"
},
diff --git a/homeassistant/components/hangouts/.translations/nn.json b/homeassistant/components/hangouts/.translations/nn.json
index 58e5f4f45fd..c8a5fb4481b 100644
--- a/homeassistant/components/hangouts/.translations/nn.json
+++ b/homeassistant/components/hangouts/.translations/nn.json
@@ -14,7 +14,7 @@
"data": {
"2fa": "2FA PIN"
},
- "title": "To-faktor-autentiserin"
+ "title": "To-faktor-autentisering"
},
"user": {
"data": {
diff --git a/homeassistant/components/hangouts/.translations/no.json b/homeassistant/components/hangouts/.translations/no.json
index d75092da759..ab061ee1a80 100644
--- a/homeassistant/components/hangouts/.translations/no.json
+++ b/homeassistant/components/hangouts/.translations/no.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "Autorisasjonskode (kreves for manuell godkjenning)",
"email": "E-postadresse",
"password": "Passord"
},
diff --git a/homeassistant/components/hangouts/.translations/pl.json b/homeassistant/components/hangouts/.translations/pl.json
index 5e0ecfa2900..5da1e219799 100644
--- a/homeassistant/components/hangouts/.translations/pl.json
+++ b/homeassistant/components/hangouts/.translations/pl.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "Kod autoryzacji (wymagany do r\u0119cznego uwierzytelnienia)",
"email": "Adres e-mail",
"password": "Has\u0142o"
},
diff --git a/homeassistant/components/hangouts/.translations/ru.json b/homeassistant/components/hangouts/.translations/ru.json
index 143d6bc88ba..52b8798c0f4 100644
--- a/homeassistant/components/hangouts/.translations/ru.json
+++ b/homeassistant/components/hangouts/.translations/ru.json
@@ -18,10 +18,11 @@
},
"user": {
"data": {
+ "authorization_code": "\u041a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438)",
"email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b",
"password": "\u041f\u0430\u0440\u043e\u043b\u044c"
},
- "title": "\u0412\u0445\u043e\u0434 \u0432 Google Hangouts"
+ "title": "Google Hangouts"
}
},
"title": "Google Hangouts"
diff --git a/homeassistant/components/hangouts/.translations/sl.json b/homeassistant/components/hangouts/.translations/sl.json
index db85f338b67..64ca6da10ac 100644
--- a/homeassistant/components/hangouts/.translations/sl.json
+++ b/homeassistant/components/hangouts/.translations/sl.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "Koda pooblastila (potrebna za ro\u010dno overjanje)",
"email": "E-po\u0161tni naslov",
"password": "Geslo"
},
diff --git a/homeassistant/components/hangouts/.translations/th.json b/homeassistant/components/hangouts/.translations/th.json
index 7b6645edca2..bcc59392e2e 100644
--- a/homeassistant/components/hangouts/.translations/th.json
+++ b/homeassistant/components/hangouts/.translations/th.json
@@ -6,6 +6,7 @@
},
"user": {
"data": {
+ "email": "\u0e17\u0e35\u0e48\u0e2d\u0e22\u0e39\u0e48\u0e2d\u0e35\u0e40\u0e21\u0e25",
"password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19"
},
"description": "\u0e27\u0e48\u0e32\u0e07\u0e40\u0e1b\u0e25\u0e48\u0e32"
diff --git a/homeassistant/components/hangouts/.translations/zh-Hant.json b/homeassistant/components/hangouts/.translations/zh-Hant.json
index 16234acb193..c8da604e6f2 100644
--- a/homeassistant/components/hangouts/.translations/zh-Hant.json
+++ b/homeassistant/components/hangouts/.translations/zh-Hant.json
@@ -19,6 +19,7 @@
},
"user": {
"data": {
+ "authorization_code": "\u9a57\u8b49\u78bc\uff08\u624b\u52d5\u9a57\u8b49\u5fc5\u9808\uff09",
"email": "\u96fb\u5b50\u90f5\u4ef6",
"password": "\u5bc6\u78bc"
},
diff --git a/homeassistant/components/hangouts/__init__.py b/homeassistant/components/hangouts/__init__.py
index 2d36de8b769..50936ac62a0 100644
--- a/homeassistant/components/hangouts/__init__.py
+++ b/homeassistant/components/hangouts/__init__.py
@@ -19,8 +19,6 @@ from .const import (
MESSAGE_SCHEMA, SERVICE_RECONNECT, SERVICE_SEND_MESSAGE, SERVICE_UPDATE,
TARGETS_SCHEMA)
-REQUIREMENTS = ['hangups==0.4.6']
-
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/hangouts/config_flow.py b/homeassistant/components/hangouts/config_flow.py
index 5eecc24d45e..743c49abfdf 100644
--- a/homeassistant/components/hangouts/config_flow.py
+++ b/homeassistant/components/hangouts/config_flow.py
@@ -1,11 +1,14 @@
"""Config flow to configure Google Hangouts."""
+import functools
import voluptuous as vol
+
from homeassistant import config_entries
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import callback
-from .const import CONF_2FA, CONF_REFRESH_TOKEN, DOMAIN as HANGOUTS_DOMAIN
+from .const import CONF_2FA, CONF_REFRESH_TOKEN, CONF_AUTH_CODE, \
+ DOMAIN as HANGOUTS_DOMAIN
@callback
@@ -41,13 +44,24 @@ class HangoutsFlowHandler(config_entries.ConfigFlow):
from .hangups_utils import (HangoutsCredentials,
HangoutsRefreshToken,
GoogleAuthError, Google2FAError)
- self._credentials = HangoutsCredentials(user_input[CONF_EMAIL],
- user_input[CONF_PASSWORD])
+ user_email = user_input[CONF_EMAIL]
+ user_password = user_input[CONF_PASSWORD]
+ user_auth_code = user_input.get(CONF_AUTH_CODE)
+ manual_login = user_auth_code is not None
+
+ user_pin = None
+ self._credentials = HangoutsCredentials(user_email,
+ user_password,
+ user_pin,
+ user_auth_code)
self._refresh_token = HangoutsRefreshToken(None)
try:
- await self.hass.async_add_executor_job(get_auth,
- self._credentials,
- self._refresh_token)
+ await self.hass.async_add_executor_job(
+ functools.partial(get_auth,
+ self._credentials,
+ self._refresh_token,
+ manual_login=manual_login)
+ )
return await self.async_step_final()
except GoogleAuthError as err:
@@ -63,7 +77,8 @@ class HangoutsFlowHandler(config_entries.ConfigFlow):
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_EMAIL): str,
- vol.Required(CONF_PASSWORD): str
+ vol.Required(CONF_PASSWORD): str,
+ vol.Optional(CONF_AUTH_CODE): str
}),
errors=errors
)
diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py
index 38b238292b3..f664e769b9f 100644
--- a/homeassistant/components/hangouts/const.py
+++ b/homeassistant/components/hangouts/const.py
@@ -13,6 +13,7 @@ _LOGGER = logging.getLogger('.')
DOMAIN = 'hangouts'
CONF_2FA = '2fa'
+CONF_AUTH_CODE = 'authorization_code'
CONF_REFRESH_TOKEN = 'refresh_token'
CONF_BOT = 'bot'
diff --git a/homeassistant/components/hangouts/hangups_utils.py b/homeassistant/components/hangouts/hangups_utils.py
index 9aff7730201..d2556ac15a0 100644
--- a/homeassistant/components/hangouts/hangups_utils.py
+++ b/homeassistant/components/hangouts/hangups_utils.py
@@ -13,7 +13,7 @@ class HangoutsCredentials(CredentialsPrompt):
This implementation gets the user data as params.
"""
- def __init__(self, email, password, pin=None):
+ def __init__(self, email, password, pin=None, auth_code=None):
"""Google account credentials.
:param email: Google account email address.
@@ -23,6 +23,7 @@ class HangoutsCredentials(CredentialsPrompt):
self._email = email
self._password = password
self._pin = pin
+ self._auth_code = auth_code
def get_email(self):
"""Return email.
@@ -54,6 +55,20 @@ class HangoutsCredentials(CredentialsPrompt):
"""
self._pin = pin
+ def get_authorization_code(self):
+ """Return the oauth authorization code.
+
+ :return: Google oauth code.
+ """
+ return self._auth_code
+
+ def set_authorization_code(self, code):
+ """Set the google oauth authorization code.
+
+ :param code: Oauth code returned after authentication with google.
+ """
+ self._auth_code = code
+
class HangoutsRefreshToken(RefreshTokenCache):
"""Memory-based cache for refresh token."""
diff --git a/homeassistant/components/hangouts/manifest.json b/homeassistant/components/hangouts/manifest.json
new file mode 100644
index 00000000000..a17bd76adb4
--- /dev/null
+++ b/homeassistant/components/hangouts/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hangouts",
+ "name": "Hangouts",
+ "documentation": "https://www.home-assistant.io/components/hangouts",
+ "requirements": [
+ "hangups==0.4.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hangouts/notify.py b/homeassistant/components/hangouts/notify.py
index de9af2e0775..e88f80afbcd 100644
--- a/homeassistant/components/hangouts/notify.py
+++ b/homeassistant/components/hangouts/notify.py
@@ -12,8 +12,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = [DOMAIN]
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEFAULT_CONVERSATIONS): [TARGETS_SCHEMA]
})
diff --git a/homeassistant/components/hangouts/strings.json b/homeassistant/components/hangouts/strings.json
index c83a0ae0876..8c155784ebe 100644
--- a/homeassistant/components/hangouts/strings.json
+++ b/homeassistant/components/hangouts/strings.json
@@ -13,7 +13,8 @@
"user": {
"data": {
"email": "E-Mail Address",
- "password": "Password"
+ "password": "Password",
+ "authorization_code": "Authorization Code (required for manual authentication)"
},
"title": "Google Hangouts Login"
},
diff --git a/homeassistant/components/harman_kardon_avr/manifest.json b/homeassistant/components/harman_kardon_avr/manifest.json
new file mode 100644
index 00000000000..eecbf0edd63
--- /dev/null
+++ b/homeassistant/components/harman_kardon_avr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "harman_kardon_avr",
+ "name": "Harman kardon avr",
+ "documentation": "https://www.home-assistant.io/components/harman_kardon_avr",
+ "requirements": [
+ "hkavr==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/harman_kardon_avr/media_player.py b/homeassistant/components/harman_kardon_avr/media_player.py
index 334757c086d..dc200f39b9c 100644
--- a/homeassistant/components/harman_kardon_avr/media_player.py
+++ b/homeassistant/components/harman_kardon_avr/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with an Harman/Kardon or JBL AVR.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.harman_kardon_avr/
-"""
+"""Support for interface with an Harman/Kardon or JBL AVR."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON)
-REQUIREMENTS = ['hkavr==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Harman Kardon AVR'
diff --git a/homeassistant/components/harmony/manifest.json b/homeassistant/components/harmony/manifest.json
new file mode 100644
index 00000000000..b2f9e69e014
--- /dev/null
+++ b/homeassistant/components/harmony/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "harmony",
+ "name": "Harmony",
+ "documentation": "https://www.home-assistant.io/components/harmony",
+ "requirements": [
+ "aioharmony==0.1.11"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ehendrix23"
+ ]
+}
diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py
index 78f2674243c..c4aebb1bdcb 100644
--- a/homeassistant/components/harmony/remote.py
+++ b/homeassistant/components/harmony/remote.py
@@ -16,8 +16,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
from homeassistant.util import slugify
-REQUIREMENTS = ['aioharmony==0.1.8']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CHANNEL = 'channel'
diff --git a/homeassistant/components/harmony/services.yaml b/homeassistant/components/harmony/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py
index e8d04b1596d..c8c0f6c9f19 100644
--- a/homeassistant/components/hassio/__init__.py
+++ b/homeassistant/components/hassio/__init__.py
@@ -16,16 +16,16 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
from homeassistant.util.dt import utcnow
-from .auth import async_setup_auth
-from .discovery import async_setup_discovery
+from .auth import async_setup_auth_view
+from .addon_panel import async_setup_addon_panel
+from .discovery import async_setup_discovery_view
from .handler import HassIO, HassioAPIError
from .http import HassIOView
-from .ingress import async_setup_ingress
+from .ingress import async_setup_ingress_view
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'hassio'
-DEPENDENCIES = ['http']
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
@@ -266,12 +266,15 @@ async def async_setup(hass, config):
HASS_DOMAIN, service, async_handle_core_service)
# Init discovery Hass.io feature
- async_setup_discovery(hass, hassio, config)
+ async_setup_discovery_view(hass, hassio)
# Init auth Hass.io feature
- async_setup_auth(hass)
+ async_setup_auth_view(hass)
# Init ingress Hass.io feature
- async_setup_ingress(hass, host)
+ async_setup_ingress_view(hass, host)
+
+ # Init add-on ingress panels
+ await async_setup_addon_panel(hass, hassio)
return True
diff --git a/homeassistant/components/hassio/addon_panel.py b/homeassistant/components/hassio/addon_panel.py
new file mode 100644
index 00000000000..7291a87e954
--- /dev/null
+++ b/homeassistant/components/hassio/addon_panel.py
@@ -0,0 +1,93 @@
+"""Implement the Ingress Panel feature for Hass.io Add-ons."""
+import asyncio
+import logging
+
+from aiohttp import web
+
+from homeassistant.components.http import HomeAssistantView
+from homeassistant.helpers.typing import HomeAssistantType
+
+from .const import ATTR_PANELS, ATTR_TITLE, ATTR_ICON, ATTR_ADMIN, ATTR_ENABLE
+from .handler import HassioAPIError
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_addon_panel(hass: HomeAssistantType, hassio):
+ """Add-on Ingress Panel setup."""
+ hassio_addon_panel = HassIOAddonPanel(hass, hassio)
+ hass.http.register_view(hassio_addon_panel)
+
+ # If panels are exists
+ panels = await hassio_addon_panel.get_panels()
+ if not panels:
+ return
+
+ # Register available panels
+ jobs = []
+ for addon, data in panels.items():
+ if not data[ATTR_ENABLE]:
+ continue
+ jobs.append(_register_panel(hass, addon, data))
+
+ if jobs:
+ await asyncio.wait(jobs)
+
+
+class HassIOAddonPanel(HomeAssistantView):
+ """Hass.io view to handle base part."""
+
+ name = "api:hassio_push:panel"
+ url = "/api/hassio_push/panel/{addon}"
+
+ def __init__(self, hass, hassio):
+ """Initialize WebView."""
+ self.hass = hass
+ self.hassio = hassio
+
+ async def post(self, request, addon):
+ """Handle new add-on panel requests."""
+ panels = await self.get_panels()
+
+ # Panel exists for add-on slug
+ if addon not in panels or not panels[addon][ATTR_ENABLE]:
+ _LOGGER.error("Panel is not enable for %s", addon)
+ return web.Response(status=400)
+ data = panels[addon]
+
+ # Register panel
+ await _register_panel(self.hass, addon, data)
+ return web.Response()
+
+ async def delete(self, request, addon):
+ """Handle remove add-on panel requests."""
+ # Currently not supported by backend / frontend
+ return web.Response()
+
+ async def get_panels(self):
+ """Return panels add-on info data."""
+ try:
+ data = await self.hassio.get_ingress_panels()
+ return data[ATTR_PANELS]
+ except HassioAPIError as err:
+ _LOGGER.error("Can't read panel info: %s", err)
+ return {}
+
+
+def _register_panel(hass, addon, data):
+ """Init coroutine to register the panel.
+
+ Return coroutine.
+ """
+ return hass.components.panel_custom.async_register_panel(
+ frontend_url_path=addon,
+ webcomponent_name='hassio-main',
+ sidebar_title=data[ATTR_TITLE],
+ sidebar_icon=data[ATTR_ICON],
+ js_url='/api/hassio/app/entrypoint.js',
+ embed_iframe=True,
+ require_admin=data[ATTR_ADMIN],
+ config={
+ "ingress": addon
+ }
+ )
diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py
index 05c183ccd60..85ae6473562 100644
--- a/homeassistant/components/hassio/auth.py
+++ b/homeassistant/components/hassio/auth.py
@@ -1,18 +1,19 @@
"""Implement the auth feature from Hass.io for Add-ons."""
-from ipaddress import ip_address
import logging
import os
+from ipaddress import ip_address
+import voluptuous as vol
from aiohttp import web
from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound
-import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.const import KEY_REAL_IP
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
-import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.typing import HomeAssistantType
from .const import ATTR_ADDON, ATTR_PASSWORD, ATTR_USERNAME
@@ -27,7 +28,7 @@ SCHEMA_API_AUTH = vol.Schema({
@callback
-def async_setup_auth(hass):
+def async_setup_auth_view(hass: HomeAssistantType):
"""Auth setup."""
hassio_auth = HassIOAuth(hass)
hass.http.register_view(hassio_auth)
diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py
index e4132562c31..9656346cd2c 100644
--- a/homeassistant/components/hassio/const.py
+++ b/homeassistant/components/hassio/const.py
@@ -1,5 +1,6 @@
"""Hass.io const variables."""
+ATTR_ADDONS = 'addons'
ATTR_DISCOVERY = 'discovery'
ATTR_ADDON = 'addon'
ATTR_NAME = 'name'
@@ -8,6 +9,11 @@ ATTR_CONFIG = 'config'
ATTR_UUID = 'uuid'
ATTR_USERNAME = 'username'
ATTR_PASSWORD = 'password'
+ATTR_PANELS = 'panels'
+ATTR_ENABLE = 'enable'
+ATTR_TITLE = 'title'
+ATTR_ICON = 'icon'
+ATTR_ADMIN = 'admin'
X_HASSIO = 'X-Hassio-Key'
X_INGRESS_PATH = "X-Ingress-Path"
diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py
index 09a98edc148..90953d634c3 100644
--- a/homeassistant/components/hassio/discovery.py
+++ b/homeassistant/components/hassio/discovery.py
@@ -5,9 +5,9 @@ import logging
from aiohttp import web
from aiohttp.web_exceptions import HTTPServiceUnavailable
-from homeassistant.components.http import HomeAssistantView
from homeassistant.const import EVENT_HOMEASSISTANT_START
-from homeassistant.core import CoreState, callback
+from homeassistant.core import callback
+from homeassistant.components.http import HomeAssistantView
from .const import (
ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_NAME, ATTR_SERVICE,
@@ -18,12 +18,13 @@ _LOGGER = logging.getLogger(__name__)
@callback
-def async_setup_discovery(hass, hassio, config):
+def async_setup_discovery_view(hass: HomeAssistantView, hassio):
"""Discovery setup."""
- hassio_discovery = HassIODiscovery(hass, hassio, config)
+ hassio_discovery = HassIODiscovery(hass, hassio)
+ hass.http.register_view(hassio_discovery)
# Handle exists discovery messages
- async def async_discovery_start_handler(event):
+ async def _async_discovery_start_handler(event):
"""Process all exists discovery on startup."""
try:
data = await hassio.retrieve_discovery_messages()
@@ -36,13 +37,8 @@ def async_setup_discovery(hass, hassio, config):
if jobs:
await asyncio.wait(jobs)
- if hass.state == CoreState.running:
- hass.async_create_task(async_discovery_start_handler(None))
- else:
- hass.bus.async_listen_once(
- EVENT_HOMEASSISTANT_START, async_discovery_start_handler)
-
- hass.http.register_view(hassio_discovery)
+ hass.bus.async_listen_once(
+ EVENT_HOMEASSISTANT_START, _async_discovery_start_handler)
class HassIODiscovery(HomeAssistantView):
@@ -51,11 +47,10 @@ class HassIODiscovery(HomeAssistantView):
name = "api:hassio_push:discovery"
url = "/api/hassio_push/discovery/{uuid}"
- def __init__(self, hass, hassio, config):
+ def __init__(self, hass: HomeAssistantView, hassio):
"""Initialize WebView."""
self.hass = hass
self.hassio = hassio
- self.config = config
async def post(self, request, uuid):
"""Handle new discovery requests."""
diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py
index 7eddc639690..aae1f31d486 100644
--- a/homeassistant/components/hassio/handler.py
+++ b/homeassistant/components/hassio/handler.py
@@ -81,6 +81,14 @@ class HassIO:
return self.send_command(
"/addons/{}/info".format(addon), method="get")
+ @_api_data
+ def get_ingress_panels(self):
+ """Return data for Add-on ingress panels.
+
+ This method return a coroutine.
+ """
+ return self.send_command("/ingress/panels", method="get")
+
@_api_bool
def restart_homeassistant(self):
"""Restart Home-Assistant container.
diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py
index 0ba83f1ca1b..824dee86fad 100644
--- a/homeassistant/components/hassio/ingress.py
+++ b/homeassistant/components/hassio/ingress.py
@@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
@callback
-def async_setup_ingress(hass: HomeAssistantType, host: str):
+def async_setup_ingress_view(hass: HomeAssistantType, host: str):
"""Auth setup."""
websession = hass.helpers.aiohttp_client.async_get_clientsession()
diff --git a/homeassistant/components/hassio/manifest.json b/homeassistant/components/hassio/manifest.json
new file mode 100644
index 00000000000..23095064d55
--- /dev/null
+++ b/homeassistant/components/hassio/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "hassio",
+ "name": "Hass.io",
+ "documentation": "https://www.home-assistant.io/hassio",
+ "requirements": [],
+ "dependencies": [
+ "http",
+ "panel_custom"
+ ],
+ "codeowners": [
+ "@home-assistant/hass-io"
+ ]
+}
diff --git a/homeassistant/components/haveibeenpwned/manifest.json b/homeassistant/components/haveibeenpwned/manifest.json
new file mode 100644
index 00000000000..f0b0561e170
--- /dev/null
+++ b/homeassistant/components/haveibeenpwned/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "haveibeenpwned",
+ "name": "Haveibeenpwned",
+ "documentation": "https://www.home-assistant.io/components/haveibeenpwned",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py
index a4ae2349e24..72cc5ced3ef 100644
--- a/homeassistant/components/haveibeenpwned/sensor.py
+++ b/homeassistant/components/haveibeenpwned/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for haveibeenpwned (email breaches) sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.haveibeenpwned/
-"""
+"""Support for haveibeenpwned (email breaches) sensor."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/hddtemp/manifest.json b/homeassistant/components/hddtemp/manifest.json
new file mode 100644
index 00000000000..2d34d3b4e7b
--- /dev/null
+++ b/homeassistant/components/hddtemp/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "hddtemp",
+ "name": "Hddtemp",
+ "documentation": "https://www.home-assistant.io/components/hddtemp",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py
index 52514a2de39..6d96f244f58 100644
--- a/homeassistant/components/hddtemp/sensor.py
+++ b/homeassistant/components/hddtemp/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for getting the disk temperature of a host.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.hddtemp/
-"""
+"""Support for getting the disk temperature of a host."""
import logging
from datetime import timedelta
from telnetlib import Telnet
diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py
index 8eb13c5ab21..189cc748d5d 100644
--- a/homeassistant/components/hdmi_cec/__init__.py
+++ b/homeassistant/components/hdmi_cec/__init__.py
@@ -17,8 +17,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyCEC==0.4.13']
-
DOMAIN = 'hdmi_cec'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json
new file mode 100644
index 00000000000..b59d5622821
--- /dev/null
+++ b/homeassistant/components/hdmi_cec/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hdmi_cec",
+ "name": "Hdmi cec",
+ "documentation": "https://www.home-assistant.io/components/hdmi_cec",
+ "requirements": [
+ "pyCEC==0.4.13"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py
index b2d2910e145..4468fd9d648 100644
--- a/homeassistant/components/hdmi_cec/media_player.py
+++ b/homeassistant/components/hdmi_cec/media_player.py
@@ -11,8 +11,6 @@ from homeassistant.const import (
from . import ATTR_NEW, CecDevice
-DEPENDENCIES = ['hdmi_cec']
-
_LOGGER = logging.getLogger(__name__)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
diff --git a/homeassistant/components/hdmi_cec/services.yaml b/homeassistant/components/hdmi_cec/services.yaml
index bb0f5f932ae..f2e5f0b837a 100644
--- a/homeassistant/components/hdmi_cec/services.yaml
+++ b/homeassistant/components/hdmi_cec/services.yaml
@@ -19,7 +19,7 @@ send_command:
are source and destination, second byte is command and optional other bytes
are command parameters. If raw command specified, other params are ignored.',
example: '"10:36"'}
- src: {desctiption: 'Source of command. Could be decimal number or string with
+ src: {description: 'Source of command. Could be decimal number or string with
hexadeximal notation: "0x10".', example: 12 or "0xc"}
standby: {description: Standby all devices which supports it.}
update: {description: Update devices state from network.}
diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py
index 639f545707e..9fb003f6d6a 100644
--- a/homeassistant/components/hdmi_cec/switch.py
+++ b/homeassistant/components/hdmi_cec/switch.py
@@ -6,8 +6,6 @@ from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY
from . import ATTR_NEW, CecDevice
-DEPENDENCIES = ['hdmi_cec']
-
_LOGGER = logging.getLogger(__name__)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py
index ff495706be7..045ffdd34c5 100644
--- a/homeassistant/components/heatmiser/climate.py
+++ b/homeassistant/components/heatmiser/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for the PRT Heatmiser themostats using the V3 protocol.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.heatmiser/
-"""
+"""Support for the PRT Heatmiser themostats using the V3 protocol."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import (
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['heatmiserV3==0.9.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_IPADDRESS = 'ipaddress'
diff --git a/homeassistant/components/heatmiser/manifest.json b/homeassistant/components/heatmiser/manifest.json
new file mode 100644
index 00000000000..0a11aecd079
--- /dev/null
+++ b/homeassistant/components/heatmiser/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "heatmiser",
+ "name": "Heatmiser",
+ "documentation": "https://www.home-assistant.io/components/heatmiser",
+ "requirements": [
+ "heatmiserV3==0.9.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/heos/.translations/ca.json b/homeassistant/components/heos/.translations/ca.json
new file mode 100644
index 00000000000..1d675ab1cd7
--- /dev/null
+++ b/homeassistant/components/heos/.translations/ca.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Nom\u00e9s pots configurar una \u00fanica connexi\u00f3 de Heos tot i que aquesta ja pot controlar tots els dispositius de la xarxa."
+ },
+ "error": {
+ "connection_failure": "No es pot connectar amb l'amfitri\u00f3 especificat."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Amfitri\u00f3",
+ "host": "Amfitri\u00f3"
+ },
+ "description": "Introdueix el nom d'amfitri\u00f3 o l'adre\u00e7a IP d'un dispositiu Heos (preferiblement un connectat a la xarxa per cable).",
+ "title": "Connexi\u00f3 amb Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/de.json b/homeassistant/components/heos/.translations/de.json
new file mode 100644
index 00000000000..e8f4df930db
--- /dev/null
+++ b/homeassistant/components/heos/.translations/de.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Es kann nur eine einzige Heos-Verbindung konfiguriert werden, da diese alle Ger\u00e4te im Netzwerk unterst\u00fctzt."
+ },
+ "error": {
+ "connection_failure": "Es kann keine Verbindung zum angegebenen Host hergestellt werden."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Host",
+ "host": "Host"
+ },
+ "description": "Bitte gib den Hostnamen oder die IP-Adresse eines Heos-Ger\u00e4ts ein (vorzugsweise eines, das per Kabel mit dem Netzwerk verbunden ist).",
+ "title": "Mit Heos verbinden"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/en.json b/homeassistant/components/heos/.translations/en.json
new file mode 100644
index 00000000000..6d4d83192c7
--- /dev/null
+++ b/homeassistant/components/heos/.translations/en.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "You can only configure a single Heos connection as it will support all devices on the network."
+ },
+ "error": {
+ "connection_failure": "Unable to connect to the specified host."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Host",
+ "host": "Host"
+ },
+ "description": "Please enter the host name or IP address of a Heos device (preferably one connected via wire to the network).",
+ "title": "Connect to Heos"
+ }
+ },
+ "title": "HEOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/es-419.json b/homeassistant/components/heos/.translations/es-419.json
new file mode 100644
index 00000000000..12ed8cc457a
--- /dev/null
+++ b/homeassistant/components/heos/.translations/es-419.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/es.json b/homeassistant/components/heos/.translations/es.json
new file mode 100644
index 00000000000..beba4aea6f1
--- /dev/null
+++ b/homeassistant/components/heos/.translations/es.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Solo puedes configurar una \u00fanica conexi\u00f3n Heos, ya que admitir\u00e1 todos los dispositivos de la red."
+ },
+ "error": {
+ "connection_failure": "No se puede conectar al host especificado."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Host",
+ "host": "Host"
+ },
+ "description": "Introduce el nombre de host o direcci\u00f3n IP de un dispositivo Heos (preferiblemente conectado por cable a la red).",
+ "title": "Conectar a Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/fr.json b/homeassistant/components/heos/.translations/fr.json
new file mode 100644
index 00000000000..274075af749
--- /dev/null
+++ b/homeassistant/components/heos/.translations/fr.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Vous ne pouvez configurer qu'une seule connexion Heos, car celle-ci supportera tous les p\u00e9riph\u00e9riques du r\u00e9seau."
+ },
+ "error": {
+ "connection_failure": "Impossible de se connecter \u00e0 l'h\u00f4te sp\u00e9cifi\u00e9."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "H\u00f4te"
+ },
+ "description": "Veuillez saisir le nom d\u2019h\u00f4te ou l\u2019adresse IP d\u2019un p\u00e9riph\u00e9rique Heos (de pr\u00e9f\u00e9rence connect\u00e9 au r\u00e9seau filaire).",
+ "title": "Se connecter \u00e0 Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/it.json b/homeassistant/components/heos/.translations/it.json
new file mode 100644
index 00000000000..32667d0dbe8
--- /dev/null
+++ b/homeassistant/components/heos/.translations/it.json
@@ -0,0 +1,17 @@
+{
+ "config": {
+ "error": {
+ "connection_failure": "Impossibile connettersi all'host specificato."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Host"
+ },
+ "description": "Inserire il nome host o l'indirizzo IP di un dispositivo Heos (preferibilmente uno connesso alla rete tramite cavo).",
+ "title": "Connetti a Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/ko.json b/homeassistant/components/heos/.translations/ko.json
new file mode 100644
index 00000000000..d02f48a3faf
--- /dev/null
+++ b/homeassistant/components/heos/.translations/ko.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Heos \uc5f0\uacb0\uc740 \ub124\ud2b8\uc6cc\ud06c\uc0c1\uc758 \ubaa8\ub4e0 \uae30\uae30\ub97c \uc9c0\uc6d0\ud558\uae30 \ub54c\ubb38\uc5d0 \ud558\ub098\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
+ },
+ "error": {
+ "connection_failure": "\uc9c0\uc815\ub41c \ud638\uc2a4\ud2b8\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "\ud638\uc2a4\ud2b8",
+ "host": "\ud638\uc2a4\ud2b8"
+ },
+ "description": "Heos \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. (\uc720\uc120 \ub124\ud2b8\uc6cc\ud06c\ub85c \uc5f0\uacb0\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4)",
+ "title": "Heos \uc5f0\uacb0"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/lb.json b/homeassistant/components/heos/.translations/lb.json
new file mode 100644
index 00000000000..416f0878de4
--- /dev/null
+++ b/homeassistant/components/heos/.translations/lb.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Dir k\u00ebnnt n\u00ebmmen eng eenzeg Heos Verbindung konfigur\u00e9ieren, well se all Apparater am Netzwierk \u00ebnnerst\u00ebtzen."
+ },
+ "error": {
+ "connection_failure": "Kann sech net mat dem spezifiz\u00e9ierten Apparat verbannen."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Apparat",
+ "host": "Apparat"
+ },
+ "description": "Gitt den Numm oder IP-Adress vun engem Heos-Apparat an (am beschten iwwer Kabel mam Reseau verbonnen).",
+ "title": "Mat Heos verbannen"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/nn.json b/homeassistant/components/heos/.translations/nn.json
new file mode 100644
index 00000000000..ec2dc294500
--- /dev/null
+++ b/homeassistant/components/heos/.translations/nn.json
@@ -0,0 +1,12 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Vert"
+ }
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/no.json b/homeassistant/components/heos/.translations/no.json
new file mode 100644
index 00000000000..f7595d80b96
--- /dev/null
+++ b/homeassistant/components/heos/.translations/no.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Du kan kun konfigurere en enkelt Heos tilkobling, da den st\u00f8tter alle enhetene p\u00e5 nettverket."
+ },
+ "error": {
+ "connection_failure": "Kan ikke koble til den angitte verten."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Vert"
+ },
+ "description": "Vennligst skriv inn vertsnavnet eller IP-adressen til en Heos-enhet (helst en tilkoblet via kabel til nettverket).",
+ "title": "Koble til Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/pl.json b/homeassistant/components/heos/.translations/pl.json
new file mode 100644
index 00000000000..faa104d20ea
--- /dev/null
+++ b/homeassistant/components/heos/.translations/pl.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno po\u0142\u0105czenie Heos, poniewa\u017c b\u0119dzie ono obs\u0142ugiwa\u0107 wszystkie urz\u0105dzenia w sieci."
+ },
+ "error": {
+ "connection_failure": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z okre\u015blonym hostem."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Host"
+ },
+ "description": "Wprowad\u017a nazw\u0119 hosta lub adres IP urz\u0105dzenia Heos (preferowane po\u0142\u0105czenie kablowe, nie WiFi).",
+ "title": "Po\u0142\u0105cz si\u0119 z Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/pt.json b/homeassistant/components/heos/.translations/pt.json
new file mode 100644
index 00000000000..33c83fdc738
--- /dev/null
+++ b/homeassistant/components/heos/.translations/pt.json
@@ -0,0 +1,12 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Servidor"
+ }
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/ru.json b/homeassistant/components/heos/.translations/ru.json
new file mode 100644
index 00000000000..f19b5e52064
--- /dev/null
+++ b/homeassistant/components/heos/.translations/ru.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "\u041d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 HEOS \u0432 \u0441\u0435\u0442\u0438."
+ },
+ "error": {
+ "connection_failure": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c\u0443 \u0445\u043e\u0441\u0442\u0443"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "\u0425\u043e\u0441\u0442",
+ "host": "\u0425\u043e\u0441\u0442"
+ },
+ "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 HEOS (\u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0441\u0435\u0442\u0438 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0431\u0435\u043b\u044c).",
+ "title": "HEOS"
+ }
+ },
+ "title": "HEOS"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/sl.json b/homeassistant/components/heos/.translations/sl.json
new file mode 100644
index 00000000000..1e84381e7a6
--- /dev/null
+++ b/homeassistant/components/heos/.translations/sl.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Konfigurirate lahko samo eno povezavo Heos, le ta bo podpirala vse naprave v omre\u017eju."
+ },
+ "error": {
+ "connection_failure": "Ni mogo\u010de vzpostaviti povezave z dolo\u010denim gostiteljem."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "Gostitelj"
+ },
+ "description": "Vnesite ime gostitelja ali naslov IP naprave Heos (po mo\u017enosti eno, ki je z omre\u017ejem povezana \u017ei\u010dno).",
+ "title": "Pove\u017eite se z Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/sv.json b/homeassistant/components/heos/.translations/sv.json
new file mode 100644
index 00000000000..6e5d825ef26
--- /dev/null
+++ b/homeassistant/components/heos/.translations/sv.json
@@ -0,0 +1,17 @@
+{
+ "config": {
+ "error": {
+ "connection_failure": "Det gick inte att ansluta till den angivna v\u00e4rden."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "V\u00e4rd"
+ },
+ "description": "Ange v\u00e4rdnamnet eller IP-adressen f\u00f6r en Heos-enhet (helst en ansluten via kabel till n\u00e4tverket).",
+ "title": "Anslut till Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/.translations/zh-Hant.json b/homeassistant/components/heos/.translations/zh-Hant.json
new file mode 100644
index 00000000000..8e49922709c
--- /dev/null
+++ b/homeassistant/components/heos/.translations/zh-Hant.json
@@ -0,0 +1,21 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Heos \u9023\u7dda\uff0c\u5c07\u652f\u63f4\u7db2\u8def\u4e2d\u6240\u6709\u5c0d\u61c9\u88dd\u7f6e\u3002"
+ },
+ "error": {
+ "connection_failure": "\u7121\u6cd5\u9023\u7dda\u81f3\u6307\u5b9a\u4e3b\u6a5f\u7aef\u3002"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "access_token": "\u4e3b\u6a5f\u7aef",
+ "host": "\u4e3b\u6a5f\u7aef"
+ },
+ "description": "\u8acb\u8f38\u5165\u4e3b\u6a5f\u6bb5\u540d\u7a31\u6216 Heos \u88dd\u7f6e IP \u4f4d\u5740\uff08\u5df2\u900f\u904e\u6709\u7dda\u7db2\u8def\u9023\u7dda\uff09\u3002",
+ "title": "\u9023\u7dda\u81f3 Heos"
+ }
+ },
+ "title": "Heos"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py
new file mode 100644
index 00000000000..529ee27997e
--- /dev/null
+++ b/homeassistant/components/heos/__init__.py
@@ -0,0 +1,212 @@
+"""Denon HEOS Media Player."""
+import asyncio
+from datetime import timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.media_player.const import (
+ DOMAIN as MEDIA_PLAYER_DOMAIN)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
+from homeassistant.exceptions import ConfigEntryNotReady
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.typing import ConfigType, HomeAssistantType
+from homeassistant.util import Throttle
+
+from .config_flow import format_title
+from .const import (
+ COMMAND_RETRY_ATTEMPTS, COMMAND_RETRY_DELAY, DATA_CONTROLLER,
+ DATA_SOURCE_MANAGER, DOMAIN, SIGNAL_HEOS_SOURCES_UPDATED)
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_HOST): cv.string
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+MIN_UPDATE_SOURCES = timedelta(seconds=1)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup(hass: HomeAssistantType, config: ConfigType):
+ """Set up the HEOS component."""
+ if DOMAIN not in config:
+ return True
+ host = config[DOMAIN][CONF_HOST]
+ entries = hass.config_entries.async_entries(DOMAIN)
+ if not entries:
+ # Create new entry based on config
+ hass.async_create_task(
+ hass.config_entries.flow.async_init(
+ DOMAIN, context={'source': 'import'},
+ data={CONF_HOST: host}))
+ else:
+ # Check if host needs to be updated
+ entry = entries[0]
+ if entry.data[CONF_HOST] != host:
+ entry.data[CONF_HOST] = host
+ entry.title = format_title(host)
+ hass.config_entries.async_update_entry(entry)
+
+ return True
+
+
+async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
+ """Initialize config entry which represents the HEOS controller."""
+ from pyheos import Heos, CommandError
+ host = entry.data[CONF_HOST]
+ # Setting all_progress_events=False ensures that we only receive a
+ # media position update upon start of playback or when media changes
+ controller = Heos(host, all_progress_events=False)
+ try:
+ await controller.connect(auto_reconnect=True)
+ # Auto reconnect only operates if initial connection was successful.
+ except (asyncio.TimeoutError, ConnectionError, CommandError) as error:
+ await controller.disconnect()
+ _LOGGER.debug("Unable to connect to controller %s: %s", host, error)
+ raise ConfigEntryNotReady
+
+ # Disconnect when shutting down
+ async def disconnect_controller(event):
+ await controller.disconnect()
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect_controller)
+
+ # Get players and sources
+ try:
+ players = await controller.get_players()
+ favorites = {}
+ if controller.is_signed_in:
+ favorites = await controller.get_favorites()
+ else:
+ _LOGGER.warning("%s is not logged in to your HEOS account and will"
+ " be unable to retrieve your favorites", host)
+ inputs = await controller.get_input_sources()
+ except (asyncio.TimeoutError, ConnectionError, CommandError) as error:
+ await controller.disconnect()
+ _LOGGER.debug("Unable to retrieve players and sources: %s", error,
+ exc_info=isinstance(error, CommandError))
+ raise ConfigEntryNotReady
+
+ source_manager = SourceManager(favorites, inputs)
+ source_manager.connect_update(hass, controller)
+
+ hass.data[DOMAIN] = {
+ DATA_CONTROLLER: controller,
+ DATA_SOURCE_MANAGER: source_manager,
+ MEDIA_PLAYER_DOMAIN: players
+ }
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ entry, MEDIA_PLAYER_DOMAIN))
+ return True
+
+
+async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
+ """Unload a config entry."""
+ controller = hass.data[DOMAIN][DATA_CONTROLLER]
+ controller.dispatcher.disconnect_all()
+ await controller.disconnect()
+ hass.data.pop(DOMAIN)
+ return await hass.config_entries.async_forward_entry_unload(
+ entry, MEDIA_PLAYER_DOMAIN)
+
+
+class SourceManager:
+ """Class that manages sources for players."""
+
+ def __init__(self, favorites, inputs, *,
+ retry_delay: int = COMMAND_RETRY_DELAY,
+ max_retry_attempts: int = COMMAND_RETRY_ATTEMPTS):
+ """Init input manager."""
+ self.retry_delay = retry_delay
+ self.max_retry_attempts = max_retry_attempts
+ self.favorites = favorites
+ self.inputs = inputs
+ self.source_list = self._build_source_list()
+
+ def _build_source_list(self):
+ """Build a single list of inputs from various types."""
+ source_list = []
+ source_list.extend([favorite.name for favorite
+ in self.favorites.values()])
+ source_list.extend([source.name for source in self.inputs])
+ return source_list
+
+ async def play_source(self, source: str, player):
+ """Determine type of source and play it."""
+ index = next((index for index, favorite in self.favorites.items()
+ if favorite.name == source), None)
+ if index is not None:
+ await player.play_favorite(index)
+ return
+
+ input_source = next((input_source for input_source in self.inputs
+ if input_source.name == source), None)
+ if input_source is not None:
+ await player.play_input_source(input_source)
+ return
+
+ _LOGGER.error("Unknown source: %s", source)
+
+ def get_current_source(self, now_playing_media):
+ """Determine current source from now playing media."""
+ from pyheos import const
+ # Match input by input_name:media_id
+ if now_playing_media.source_id == const.MUSIC_SOURCE_AUX_INPUT:
+ return next((input_source.name for input_source in self.inputs
+ if input_source.input_name ==
+ now_playing_media.media_id), None)
+ # Try matching favorite by name:station or media_id:album_id
+ return next((source.name for source in self.favorites.values()
+ if source.name == now_playing_media.station
+ or source.media_id == now_playing_media.album_id), None)
+
+ def connect_update(self, hass, controller):
+ """
+ Connect listener for when sources change and signal player update.
+
+ EVENT_SOURCES_CHANGED is often raised multiple times in response to a
+ physical event therefore throttle it. Retrieving sources immediately
+ after the event may fail so retry.
+ """
+ from pyheos import CommandError, const
+
+ @Throttle(MIN_UPDATE_SOURCES)
+ async def get_sources():
+ retry_attempts = 0
+ while True:
+ try:
+ favorites = {}
+ if controller.is_signed_in:
+ favorites = await controller.get_favorites()
+ inputs = await controller.get_input_sources()
+ return favorites, inputs
+ except (asyncio.TimeoutError, ConnectionError, CommandError) \
+ as error:
+ if retry_attempts < self.max_retry_attempts:
+ retry_attempts += 1
+ _LOGGER.debug("Error retrieving sources and will "
+ "retry: %s", error,
+ exc_info=isinstance(error, CommandError))
+ await asyncio.sleep(self.retry_delay)
+ else:
+ _LOGGER.error("Unable to update sources: %s", error,
+ exc_info=isinstance(error, CommandError))
+ return
+
+ async def update_sources(event):
+ if event in (const.EVENT_SOURCES_CHANGED,
+ const.EVENT_USER_CHANGED):
+ sources = await get_sources()
+ # If throttled, it will return None
+ if sources:
+ self.favorites, self.inputs = sources
+ self.source_list = self._build_source_list()
+ _LOGGER.debug("Sources updated due to changed event")
+ # Let players know to update
+ hass.helpers.dispatcher.async_dispatcher_send(
+ SIGNAL_HEOS_SOURCES_UPDATED)
+
+ controller.dispatcher.connect(
+ const.SIGNAL_CONTROLLER_EVENT, update_sources)
diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py
new file mode 100644
index 00000000000..656058877db
--- /dev/null
+++ b/homeassistant/components/heos/config_flow.py
@@ -0,0 +1,77 @@
+"""Config flow to configure Heos."""
+import asyncio
+
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.const import CONF_HOST, CONF_NAME
+
+from .const import DATA_DISCOVERED_HOSTS, DOMAIN
+
+
+def format_title(host: str) -> str:
+ """Format the title for config entries."""
+ return "Controller ({})".format(host)
+
+
+@config_entries.HANDLERS.register(DOMAIN)
+class HeosFlowHandler(config_entries.ConfigFlow):
+ """Define a flow for HEOS."""
+
+ VERSION = 1
+ CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
+
+ async def async_step_discovery(self, discovery_info):
+ """Handle a discovered Heos device."""
+ # Store discovered host
+ friendly_name = "{} ({})".format(
+ discovery_info[CONF_NAME], discovery_info[CONF_HOST])
+ self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {})
+ self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] \
+ = discovery_info[CONF_HOST]
+ # Abort if other flows in progress or an entry already exists
+ if self._async_in_progress() or self._async_current_entries():
+ return self.async_abort(reason='already_setup')
+ # Show selection form
+ return self.async_show_form(step_id='user')
+
+ async def async_step_import(self, user_input=None):
+ """Occurs when an entry is setup through config."""
+ host = user_input[CONF_HOST]
+ return self.async_create_entry(
+ title=format_title(host),
+ data={CONF_HOST: host})
+
+ async def async_step_user(self, user_input=None):
+ """Obtain host and validate connection."""
+ from pyheos import Heos
+ self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {})
+ # Only a single entry is needed for all devices
+ if self._async_current_entries():
+ return self.async_abort(reason='already_setup')
+ # Try connecting to host if provided
+ errors = {}
+ host = None
+ if user_input is not None:
+ host = user_input[CONF_HOST]
+ # Map host from friendly name if in discovered hosts
+ host = self.hass.data[DATA_DISCOVERED_HOSTS].get(host, host)
+ heos = Heos(host)
+ try:
+ await heos.connect()
+ self.hass.data.pop(DATA_DISCOVERED_HOSTS)
+ return await self.async_step_import({CONF_HOST: host})
+ except (asyncio.TimeoutError, ConnectionError):
+ errors[CONF_HOST] = 'connection_failure'
+ finally:
+ await heos.disconnect()
+
+ # Return form
+ host_type = str if not self.hass.data[DATA_DISCOVERED_HOSTS] \
+ else vol.In(list(self.hass.data[DATA_DISCOVERED_HOSTS]))
+ return self.async_show_form(
+ step_id='user',
+ data_schema=vol.Schema({
+ vol.Required(CONF_HOST, default=host): host_type
+ }),
+ errors=errors)
diff --git a/homeassistant/components/heos/const.py b/homeassistant/components/heos/const.py
new file mode 100644
index 00000000000..fc3a7fd8f30
--- /dev/null
+++ b/homeassistant/components/heos/const.py
@@ -0,0 +1,9 @@
+"""Const for the HEOS integration."""
+
+COMMAND_RETRY_ATTEMPTS = 2
+COMMAND_RETRY_DELAY = 1
+DATA_CONTROLLER = "controller"
+DATA_SOURCE_MANAGER = "source_manager"
+DATA_DISCOVERED_HOSTS = "heos_discovered_hosts"
+DOMAIN = 'heos'
+SIGNAL_HEOS_SOURCES_UPDATED = "heos_sources_updated"
diff --git a/homeassistant/components/heos/manifest.json b/homeassistant/components/heos/manifest.json
new file mode 100644
index 00000000000..97b53935614
--- /dev/null
+++ b/homeassistant/components/heos/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "heos",
+ "name": "Heos",
+ "documentation": "https://www.home-assistant.io/components/heos",
+ "requirements": [
+ "pyheos==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@andrewsayre"
+ ]
+}
diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py
new file mode 100644
index 00000000000..8821591df20
--- /dev/null
+++ b/homeassistant/components/heos/media_player.py
@@ -0,0 +1,319 @@
+"""Denon HEOS Media Player."""
+from functools import reduce, wraps
+import logging
+from operator import ior
+from typing import Sequence
+
+from homeassistant.components.media_player import MediaPlayerDevice
+from homeassistant.components.media_player.const import (
+ DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
+ SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
+ SUPPORT_VOLUME_STEP)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING
+from homeassistant.helpers.typing import HomeAssistantType
+from homeassistant.util.dt import utcnow
+
+from .const import (
+ DATA_SOURCE_MANAGER, DOMAIN as HEOS_DOMAIN, SIGNAL_HEOS_SOURCES_UPDATED)
+
+BASE_SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
+ SUPPORT_VOLUME_STEP | SUPPORT_CLEAR_PLAYLIST | \
+ SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOURCE
+
+_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: HomeAssistantType, entry: ConfigEntry,
+ async_add_entities):
+ """Add media players for a config entry."""
+ players = hass.data[HEOS_DOMAIN][DOMAIN]
+ devices = [HeosMediaPlayer(player) for player in players.values()]
+ async_add_entities(devices, True)
+
+
+def log_command_error(command: str):
+ """Return decorator that logs command failure."""
+ def decorator(func):
+ @wraps(func)
+ async def wrapper(*args, **kwargs):
+ from pyheos import CommandError
+ try:
+ await func(*args, **kwargs)
+ except CommandError as ex:
+ _LOGGER.error("Unable to %s: %s", command, ex)
+ return wrapper
+ return decorator
+
+
+class HeosMediaPlayer(MediaPlayerDevice):
+ """The HEOS player."""
+
+ def __init__(self, player):
+ """Initialize."""
+ from pyheos import const
+ self._media_position_updated_at = None
+ self._player = player
+ self._signals = []
+ self._supported_features = BASE_SUPPORTED_FEATURES
+ self._source_manager = None
+ self._play_state_to_state = {
+ const.PLAY_STATE_PLAY: STATE_PLAYING,
+ const.PLAY_STATE_STOP: STATE_IDLE,
+ const.PLAY_STATE_PAUSE: STATE_PAUSED
+ }
+ self._control_to_support = {
+ const.CONTROL_PLAY: SUPPORT_PLAY,
+ const.CONTROL_PAUSE: SUPPORT_PAUSE,
+ const.CONTROL_STOP: SUPPORT_STOP,
+ const.CONTROL_PLAY_PREVIOUS: SUPPORT_PREVIOUS_TRACK,
+ const.CONTROL_PLAY_NEXT: SUPPORT_NEXT_TRACK
+ }
+
+ async def _controller_event(self, event):
+ """Handle controller event."""
+ from pyheos import const
+ if event == const.EVENT_PLAYERS_CHANGED:
+ await self.async_update_ha_state(True)
+
+ async def _heos_event(self, event):
+ """Handle connection event."""
+ await self.async_update_ha_state(True)
+
+ async def _player_update(self, player_id, event):
+ """Handle player attribute updated."""
+ from pyheos import const
+ if self._player.player_id != player_id:
+ return
+ if event == const.EVENT_PLAYER_NOW_PLAYING_PROGRESS:
+ self._media_position_updated_at = utcnow()
+ await self.async_update_ha_state(True)
+
+ async def _sources_updated(self):
+ """Handle sources changed."""
+ await self.async_update_ha_state(True)
+
+ async def async_added_to_hass(self):
+ """Device added to hass."""
+ from pyheos import const
+ self._source_manager = self.hass.data[HEOS_DOMAIN][DATA_SOURCE_MANAGER]
+ # Update state when attributes of the player change
+ self._signals.append(self._player.heos.dispatcher.connect(
+ const.SIGNAL_PLAYER_EVENT, self._player_update))
+ # Update state when available players change
+ self._signals.append(self._player.heos.dispatcher.connect(
+ const.SIGNAL_CONTROLLER_EVENT, self._controller_event))
+ # Update state upon connect/disconnects
+ self._signals.append(self._player.heos.dispatcher.connect(
+ const.SIGNAL_HEOS_EVENT, self._heos_event))
+ # Update state when sources change
+ self._signals.append(
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ SIGNAL_HEOS_SOURCES_UPDATED, self._sources_updated))
+
+ @log_command_error("clear playlist")
+ async def async_clear_playlist(self):
+ """Clear players playlist."""
+ await self._player.clear_queue()
+
+ @log_command_error("pause")
+ async def async_media_pause(self):
+ """Send pause command."""
+ await self._player.pause()
+
+ @log_command_error("play")
+ async def async_media_play(self):
+ """Send play command."""
+ await self._player.play()
+
+ @log_command_error("move to previous track")
+ async def async_media_previous_track(self):
+ """Send previous track command."""
+ await self._player.play_previous()
+
+ @log_command_error("move to next track")
+ async def async_media_next_track(self):
+ """Send next track command."""
+ await self._player.play_next()
+
+ @log_command_error("stop")
+ async def async_media_stop(self):
+ """Send stop command."""
+ await self._player.stop()
+
+ @log_command_error("set mute")
+ async def async_mute_volume(self, mute):
+ """Mute the volume."""
+ await self._player.set_mute(mute)
+
+ @log_command_error("select source")
+ async def async_select_source(self, source):
+ """Select input source."""
+ await self._source_manager.play_source(source, self._player)
+
+ @log_command_error("set shuffle")
+ async def async_set_shuffle(self, shuffle):
+ """Enable/disable shuffle mode."""
+ await self._player.set_play_mode(self._player.repeat, shuffle)
+
+ @log_command_error("set volume level")
+ async def async_set_volume_level(self, volume):
+ """Set volume level, range 0..1."""
+ await self._player.set_volume(int(volume * 100))
+
+ async def async_update(self):
+ """Update supported features of the player."""
+ controls = self._player.now_playing_media.supported_controls
+ current_support = [self._control_to_support[control]
+ for control in controls]
+ self._supported_features = reduce(ior, current_support,
+ BASE_SUPPORTED_FEATURES)
+
+ async def async_will_remove_from_hass(self):
+ """Disconnect the device when removed."""
+ for signal_remove in self._signals:
+ signal_remove()
+ self._signals.clear()
+
+ @property
+ def available(self) -> bool:
+ """Return True if the device is available."""
+ return self._player.available
+
+ @property
+ def device_info(self) -> dict:
+ """Get attributes about the device."""
+ return {
+ 'identifiers': {
+ (DOMAIN, self._player.player_id)
+ },
+ 'name': self._player.name,
+ 'model': self._player.model,
+ 'manufacturer': 'HEOS',
+ 'sw_version': self._player.version
+ }
+
+ @property
+ def device_state_attributes(self) -> dict:
+ """Get additional attribute about the state."""
+ return {
+ 'media_album_id': self._player.now_playing_media.album_id,
+ 'media_queue_id': self._player.now_playing_media.queue_id,
+ 'media_source_id': self._player.now_playing_media.source_id,
+ 'media_station': self._player.now_playing_media.station,
+ 'media_type': self._player.now_playing_media.type
+ }
+
+ @property
+ def is_volume_muted(self) -> bool:
+ """Boolean if volume is currently muted."""
+ return self._player.is_muted
+
+ @property
+ def media_album_name(self) -> str:
+ """Album name of current playing media, music track only."""
+ return self._player.now_playing_media.album
+
+ @property
+ def media_artist(self) -> str:
+ """Artist of current playing media, music track only."""
+ return self._player.now_playing_media.artist
+
+ @property
+ def media_content_id(self) -> str:
+ """Content ID of current playing media."""
+ return self._player.now_playing_media.media_id
+
+ @property
+ def media_content_type(self) -> str:
+ """Content type of current playing media."""
+ return MEDIA_TYPE_MUSIC
+
+ @property
+ def media_duration(self):
+ """Duration of current playing media in seconds."""
+ duration = self._player.now_playing_media.duration
+ if isinstance(duration, int):
+ return duration / 1000
+ return None
+
+ @property
+ def media_position(self):
+ """Position of current playing media in seconds."""
+ # Some media doesn't have duration but reports position, return None
+ if not self._player.now_playing_media.duration:
+ return None
+ return self._player.now_playing_media.current_position / 1000
+
+ @property
+ def media_position_updated_at(self):
+ """When was the position of the current playing media valid."""
+ # Some media doesn't have duration but reports position, return None
+ if not self._player.now_playing_media.duration:
+ return None
+ return self._media_position_updated_at
+
+ @property
+ def media_image_url(self) -> str:
+ """Image url of current playing media."""
+ # May be an empty string, if so, return None
+ image_url = self._player.now_playing_media.image_url
+ return image_url if image_url else None
+
+ @property
+ def media_title(self) -> str:
+ """Title of current playing media."""
+ return self._player.now_playing_media.song
+
+ @property
+ def name(self) -> str:
+ """Return the name of the device."""
+ return self._player.name
+
+ @property
+ def should_poll(self) -> bool:
+ """No polling needed for this device."""
+ return False
+
+ @property
+ def shuffle(self) -> bool:
+ """Boolean if shuffle is enabled."""
+ return self._player.shuffle
+
+ @property
+ def source(self) -> str:
+ """Name of the current input source."""
+ return self._source_manager.get_current_source(
+ self._player.now_playing_media)
+
+ @property
+ def source_list(self) -> Sequence[str]:
+ """List of available input sources."""
+ return self._source_manager.source_list
+
+ @property
+ def state(self) -> str:
+ """State of the player."""
+ return self._play_state_to_state[self._player.state]
+
+ @property
+ def supported_features(self) -> int:
+ """Flag media player features that are supported."""
+ return self._supported_features
+
+ @property
+ def unique_id(self) -> str:
+ """Return a unique ID."""
+ return str(self._player.player_id)
+
+ @property
+ def volume_level(self) -> float:
+ """Volume level of the media player (0..1)."""
+ return self._player.volume / 100
diff --git a/homeassistant/components/heos/strings.json b/homeassistant/components/heos/strings.json
new file mode 100644
index 00000000000..b210e0ba87f
--- /dev/null
+++ b/homeassistant/components/heos/strings.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "title": "HEOS",
+ "step": {
+ "user": {
+ "title": "Connect to Heos",
+ "description": "Please enter the host name or IP address of a Heos device (preferably one connected via wire to the network).",
+ "data": {
+ "host": "Host"
+ }
+ }
+ },
+ "error": {
+ "connection_failure": "Unable to connect to the specified host."
+ },
+ "abort": {
+ "already_setup": "You can only configure a single Heos connection as it will support all devices on the network."
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py
index fdefc40d8fd..f15d6739615 100644
--- a/homeassistant/components/hikvision/binary_sensor.py
+++ b/homeassistant/components/hikvision/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Hikvision event stream events represented as binary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.hikvision/
-"""
+"""Support for Hikvision event stream events represented as binary sensors."""
import logging
from datetime import timedelta
import voluptuous as vol
@@ -18,7 +13,6 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
-REQUIREMENTS = ['pyhik==0.2.2']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
diff --git a/homeassistant/components/hikvision/manifest.json b/homeassistant/components/hikvision/manifest.json
new file mode 100644
index 00000000000..db6af975081
--- /dev/null
+++ b/homeassistant/components/hikvision/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "hikvision",
+ "name": "Hikvision",
+ "documentation": "https://www.home-assistant.io/components/hikvision",
+ "requirements": [
+ "pyhik==0.2.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@mezz64"
+ ]
+}
diff --git a/homeassistant/components/hikvisioncam/manifest.json b/homeassistant/components/hikvisioncam/manifest.json
new file mode 100644
index 00000000000..f2bb0822d17
--- /dev/null
+++ b/homeassistant/components/hikvisioncam/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hikvisioncam",
+ "name": "Hikvisioncam",
+ "documentation": "https://www.home-assistant.io/components/hikvisioncam",
+ "requirements": [
+ "hikvision==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/hikvisioncam/switch.py b/homeassistant/components/hikvisioncam/switch.py
index 3a3dec26e1d..373f84cee0e 100644
--- a/homeassistant/components/hikvisioncam/switch.py
+++ b/homeassistant/components/hikvisioncam/switch.py
@@ -1,9 +1,4 @@
-"""
-Support turning on/off motion detection on Hikvision cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.hikvision/
-"""
+"""Support turning on/off motion detection on Hikvision cameras."""
import logging
import voluptuous as vol
@@ -15,7 +10,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import ToggleEntity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['hikvision==0.4']
# This is the last working version, please test before updating
_LOGGING = logging.getLogger(__name__)
diff --git a/homeassistant/components/hipchat/manifest.json b/homeassistant/components/hipchat/manifest.json
new file mode 100644
index 00000000000..d49e05a5416
--- /dev/null
+++ b/homeassistant/components/hipchat/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hipchat",
+ "name": "Hipchat",
+ "documentation": "https://www.home-assistant.io/components/hipchat",
+ "requirements": [
+ "hipnotify==1.0.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hipchat/notify.py b/homeassistant/components/hipchat/notify.py
index 9e415171f29..f12fd1ffa76 100644
--- a/homeassistant/components/hipchat/notify.py
+++ b/homeassistant/components/hipchat/notify.py
@@ -1,9 +1,4 @@
-"""
-HipChat platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.hipchat/
-"""
+"""HipChat platform for notify component."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET,
PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['hipnotify==1.0.8']
-
_LOGGER = logging.getLogger(__name__)
CONF_COLOR = 'color'
diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py
index 7b07fac19a6..7efe4f2beb2 100644
--- a/homeassistant/components/history/__init__.py
+++ b/homeassistant/components/history/__init__.py
@@ -19,8 +19,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'history'
-DEPENDENCIES = ['recorder', 'http']
-
CONF_ORDER = 'use_include_order'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/history/manifest.json b/homeassistant/components/history/manifest.json
new file mode 100644
index 00000000000..e0989958626
--- /dev/null
+++ b/homeassistant/components/history/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "history",
+ "name": "History",
+ "documentation": "https://www.home-assistant.io/components/history",
+ "requirements": [],
+ "dependencies": [
+ "http",
+ "recorder"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/history_graph/__init__.py b/homeassistant/components/history_graph/__init__.py
index 893f3514d77..964d47d2502 100644
--- a/homeassistant/components/history_graph/__init__.py
+++ b/homeassistant/components/history_graph/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_ENTITIES, CONF_NAME, ATTR_ENTITY_ID
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
-DEPENDENCIES = ['history']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'history_graph'
diff --git a/homeassistant/components/history_graph/manifest.json b/homeassistant/components/history_graph/manifest.json
new file mode 100644
index 00000000000..fa0d437a700
--- /dev/null
+++ b/homeassistant/components/history_graph/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "history_graph",
+ "name": "History graph",
+ "documentation": "https://www.home-assistant.io/components/history_graph",
+ "requirements": [],
+ "dependencies": [
+ "history"
+ ],
+ "codeowners": [
+ "@andrey-git"
+ ]
+}
diff --git a/homeassistant/components/history_stats/manifest.json b/homeassistant/components/history_stats/manifest.json
new file mode 100644
index 00000000000..ea0abd87c28
--- /dev/null
+++ b/homeassistant/components/history_stats/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "history_stats",
+ "name": "History stats",
+ "documentation": "https://www.home-assistant.io/components/history_stats",
+ "requirements": [],
+ "dependencies": [
+ "history"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py
index f5b76c89aba..a0a08d4833e 100644
--- a/homeassistant/components/history_stats/sensor.py
+++ b/homeassistant/components/history_stats/sensor.py
@@ -1,9 +1,4 @@
-"""
-Component to make instant statistics about your history.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.history_stats/
-"""
+"""Component to make instant statistics about your history."""
import datetime
import logging
import math
@@ -25,8 +20,6 @@ from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'history_stats'
-DEPENDENCIES = ['history']
-
CONF_START = 'start'
CONF_END = 'end'
CONF_DURATION = 'duration'
diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py
index 72817ca695c..e6f68d704fd 100644
--- a/homeassistant/components/hitron_coda/device_tracker.py
+++ b/homeassistant/components/hitron_coda/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for the Hitron CODA-4582U, provided by Rogers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.hitron_coda/
-"""
+"""Support for the Hitron CODA-4582U, provided by Rogers."""
import logging
from collections import namedtuple
diff --git a/homeassistant/components/hitron_coda/manifest.json b/homeassistant/components/hitron_coda/manifest.json
new file mode 100644
index 00000000000..9f3c20fcca5
--- /dev/null
+++ b/homeassistant/components/hitron_coda/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "hitron_coda",
+ "name": "Hitron coda",
+ "documentation": "https://www.home-assistant.io/components/hitron_coda",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py
index 934c44028ac..fdda1f1f542 100644
--- a/homeassistant/components/hive/__init__.py
+++ b/homeassistant/components/hive/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
-REQUIREMENTS = ['pyhiveapi==0.2.17']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'hive'
diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py
index a0973f4d8e9..97900c2852e 100644
--- a/homeassistant/components/hive/binary_sensor.py
+++ b/homeassistant/components/hive/binary_sensor.py
@@ -3,8 +3,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import DATA_HIVE, DOMAIN
-DEPENDENCIES = ['hive']
-
DEVICETYPE_DEVICE_CLASS = {
'motionsensor': 'motion',
'contactsensor': 'opening',
diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py
index dac7feb2927..ab9b63dad60 100644
--- a/homeassistant/components/hive/climate.py
+++ b/homeassistant/components/hive/climate.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
from . import DATA_HIVE, DOMAIN
-DEPENDENCIES = ['hive']
-
HIVE_TO_HASS_STATE = {
'SCHEDULE': STATE_AUTO,
'MANUAL': STATE_HEAT,
diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py
index 3a2176c3eed..67331b12b35 100644
--- a/homeassistant/components/hive/light.py
+++ b/homeassistant/components/hive/light.py
@@ -6,8 +6,6 @@ import homeassistant.util.color as color_util
from . import DATA_HIVE, DOMAIN
-DEPENDENCIES = ['hive']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive light devices."""
diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json
new file mode 100644
index 00000000000..76403f293ac
--- /dev/null
+++ b/homeassistant/components/hive/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "hive",
+ "name": "Hive",
+ "documentation": "https://www.home-assistant.io/components/hive",
+ "requirements": [
+ "pyhiveapi==0.2.17"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@Rendili",
+ "@KJonline"
+ ]
+}
diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py
index e7b7d6b4597..b8887d27409 100644
--- a/homeassistant/components/hive/sensor.py
+++ b/homeassistant/components/hive/sensor.py
@@ -4,8 +4,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_HIVE, DOMAIN
-DEPENDENCIES = ['hive']
-
FRIENDLY_NAMES = {
'Hub_OnlineStatus': 'Hive Hub Status',
'Hive_OutsideTemperature': 'Outside Temperature',
diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py
index fd4d3d69b50..ea4094d573c 100644
--- a/homeassistant/components/hive/switch.py
+++ b/homeassistant/components/hive/switch.py
@@ -3,8 +3,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DATA_HIVE, DOMAIN
-DEPENDENCIES = ['hive']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Hive switches."""
diff --git a/homeassistant/components/hlk_sw16/__init__.py b/homeassistant/components/hlk_sw16/__init__.py
index acb604bc010..79de0bd18be 100644
--- a/homeassistant/components/hlk_sw16/__init__.py
+++ b/homeassistant/components/hlk_sw16/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_connect)
-REQUIREMENTS = ['hlk-sw16==0.0.7']
-
_LOGGER = logging.getLogger(__name__)
DATA_DEVICE_REGISTER = 'hlk_sw16_device_register'
diff --git a/homeassistant/components/hlk_sw16/manifest.json b/homeassistant/components/hlk_sw16/manifest.json
new file mode 100644
index 00000000000..5266b81ab03
--- /dev/null
+++ b/homeassistant/components/hlk_sw16/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hlk_sw16",
+ "name": "Hlk sw16",
+ "documentation": "https://www.home-assistant.io/components/hlk_sw16",
+ "requirements": [
+ "hlk-sw16==0.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hlk_sw16/switch.py b/homeassistant/components/hlk_sw16/switch.py
index 164a504fa34..b7353f037c1 100644
--- a/homeassistant/components/hlk_sw16/switch.py
+++ b/homeassistant/components/hlk_sw16/switch.py
@@ -4,9 +4,7 @@ import logging
from homeassistant.components.switch import ToggleEntity
from homeassistant.const import CONF_NAME
-from . import DATA_DEVICE_REGISTER, DOMAIN as HLK_SW16, SW16Device
-
-DEPENDENCIES = [HLK_SW16]
+from . import DATA_DEVICE_REGISTER, SW16Device
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/homeassistant/manifest.json b/homeassistant/components/homeassistant/manifest.json
new file mode 100644
index 00000000000..b612c3a9fa6
--- /dev/null
+++ b/homeassistant/components/homeassistant/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "homeassistant",
+ "name": "Home Assistant Core Integration",
+ "documentation": "https://www.home-assistant.io/components/homeassistant",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/homeassistant/services.yaml b/homeassistant/components/homeassistant/services.yaml
new file mode 100644
index 00000000000..2219564abb8
--- /dev/null
+++ b/homeassistant/components/homeassistant/services.yaml
@@ -0,0 +1,39 @@
+check_config:
+ description: Check the Home Assistant configuration files for errors. Errors will be displayed in the Home Assistant log.
+
+reload_core_config:
+ description: Reload the core configuration.
+
+restart:
+ description: Restart the Home Assistant service.
+
+stop:
+ description: Stop the Home Assistant service.
+
+toggle:
+ description: Generic service to toggle devices on/off under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services.
+ fields:
+ entity_id:
+ description: The entity_id of the device to toggle on/off.
+ example: light.living_room
+
+turn_on:
+ description: Generic service to turn devices on under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services.
+ fields:
+ entity_id:
+ description: The entity_id of the device to turn on.
+ example: light.living_room
+
+turn_off:
+ description: Generic service to turn devices off under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services.
+ fields:
+ entity_id:
+ description: The entity_id of the device to turn off.
+ example: light.living_room
+
+update_entity:
+ description: Force one or more entities to update its data
+ fields:
+ entity_id:
+ description: One or multiple entity_ids to update. Can be a list.
+ example: light.living_room
diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py
index 01979f03b9a..f524455fede 100644
--- a/homeassistant/components/homekit/__init__.py
+++ b/homeassistant/components/homekit/__init__.py
@@ -26,8 +26,6 @@ from .const import (
from .util import (
show_setup_message, validate_entity_config, validate_media_player_features)
-REQUIREMENTS = ['HAP-python==2.4.2']
-
_LOGGER = logging.getLogger(__name__)
MAX_DEVICES = 100
diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py
index 2738fafbfdb..8b0e70f616e 100644
--- a/homeassistant/components/homekit/accessories.py
+++ b/homeassistant/components/homekit/accessories.py
@@ -19,8 +19,8 @@ from homeassistant.util import dt as dt_util
from .const import (
ATTR_DISPLAY_NAME, ATTR_VALUE, BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER,
CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY,
- DEBOUNCE_TIMEOUT, EVENT_HOMEKIT_CHANGED, MANUFACTURER,
- SERV_BATTERY_SERVICE)
+ CONF_LINKED_BATTERY_SENSOR, DEBOUNCE_TIMEOUT, EVENT_HOMEKIT_CHANGED,
+ MANUFACTURER, SERV_BATTERY_SERVICE)
from .util import convert_to_float, dismiss_setup_message, show_setup_message
_LOGGER = logging.getLogger(__name__)
@@ -65,19 +65,25 @@ class HomeAccessory(Accessory):
firmware_revision=__version__, manufacturer=MANUFACTURER,
model=model, serial_number=entity_id)
self.category = category
- self.config = config
+ self.config = config or {}
self.entity_id = entity_id
self.hass = hass
self.debounce = {}
self._support_battery_level = False
self._support_battery_charging = True
+ self.linked_battery_sensor = \
+ self.config.get(CONF_LINKED_BATTERY_SENSOR)
"""Add battery service if available"""
- battery_level = self.hass.states.get(self.entity_id).attributes \
+ battery_found = self.hass.states.get(self.entity_id).attributes \
.get(ATTR_BATTERY_LEVEL)
- if battery_level is None:
+ if self.linked_battery_sensor:
+ battery_found = self.hass.states.get(
+ self.linked_battery_sensor).state
+
+ if battery_found is None:
return
- _LOGGER.debug('%s: Found battery level attribute', self.entity_id)
+ _LOGGER.debug('%s: Found battery level', self.entity_id)
self._support_battery_level = True
serv_battery = self.add_preload_service(SERV_BATTERY_SERVICE)
self._char_battery = serv_battery.configure_char(
@@ -104,6 +110,14 @@ class HomeAccessory(Accessory):
async_track_state_change(
self.hass, self.entity_id, self.update_state_callback)
+ if self.linked_battery_sensor:
+ battery_state = self.hass.states.get(self.linked_battery_sensor)
+ self.hass.async_add_job(self.update_linked_battery, None, None,
+ battery_state)
+ async_track_state_change(
+ self.hass, self.linked_battery_sensor,
+ self.update_linked_battery)
+
@ha_callback
def update_state_callback(self, entity_id=None, old_state=None,
new_state=None):
@@ -111,10 +125,16 @@ class HomeAccessory(Accessory):
_LOGGER.debug('New_state: %s', new_state)
if new_state is None:
return
- if self._support_battery_level:
+ if self._support_battery_level and not self.linked_battery_sensor:
self.hass.async_add_executor_job(self.update_battery, new_state)
self.hass.async_add_executor_job(self.update_state, new_state)
+ @ha_callback
+ def update_linked_battery(self, entity_id=None, old_state=None,
+ new_state=None):
+ """Handle linked battery sensor state change listener callback."""
+ self.hass.async_add_executor_job(self.update_battery, new_state)
+
def update_battery(self, new_state):
"""Update battery service if available.
@@ -122,6 +142,8 @@ class HomeAccessory(Accessory):
"""
battery_level = convert_to_float(
new_state.attributes.get(ATTR_BATTERY_LEVEL))
+ if self.linked_battery_sensor:
+ battery_level = convert_to_float(new_state.state)
if battery_level is None:
return
self._char_battery.set_value(battery_level)
diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py
index 1b2a4dbf05d..0a2b7a0fd5d 100644
--- a/homeassistant/components/homekit/const.py
+++ b/homeassistant/components/homekit/const.py
@@ -15,6 +15,7 @@ CONF_ENTITY_CONFIG = 'entity_config'
CONF_FEATURE = 'feature'
CONF_FEATURE_LIST = 'feature_list'
CONF_FILTER = 'filter'
+CONF_LINKED_BATTERY_SENSOR = 'linked_battery_sensor'
CONF_SAFE_MODE = 'safe_mode'
# #### Config Defaults ####
@@ -109,8 +110,8 @@ CHAR_MODEL = 'Model'
CHAR_MOTION_DETECTED = 'MotionDetected'
CHAR_NAME = 'Name'
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
-CHAR_OUTLET_IN_USE = 'OutletInUse'
CHAR_ON = 'On'
+CHAR_OUTLET_IN_USE = 'OutletInUse'
CHAR_POSITION_STATE = 'PositionState'
CHAR_ROTATION_DIRECTION = 'RotationDirection'
CHAR_ROTATION_SPEED = 'RotationSpeed'
diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json
new file mode 100644
index 00000000000..e4aabfeb6cd
--- /dev/null
+++ b/homeassistant/components/homekit/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "homekit",
+ "name": "Homekit",
+ "documentation": "https://www.home-assistant.io/components/homekit",
+ "requirements": [
+ "HAP-python==2.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@cdce8p"
+ ]
+}
diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py
index 2ba5819a202..1bf57f1b1f9 100644
--- a/homeassistant/components/homekit/util.py
+++ b/homeassistant/components/homekit/util.py
@@ -4,7 +4,7 @@ import logging
import voluptuous as vol
-from homeassistant.components import fan, media_player
+from homeassistant.components import fan, media_player, sensor
from homeassistant.const import (
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS)
from homeassistant.core import split_entity_id
@@ -12,22 +12,23 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.util.temperature as temp_util
from .const import (
- CONF_FEATURE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
- FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, HOMEKIT_NOTIFY_ID, TYPE_FAUCET,
- TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
+ CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR,
+ FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE,
+ HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
+ TYPE_SWITCH, TYPE_VALVE)
_LOGGER = logging.getLogger(__name__)
BASIC_INFO_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
+ vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN),
})
FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({
vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list,
})
-
CODE_SCHEMA = BASIC_INFO_SCHEMA.extend({
vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string),
})
@@ -147,6 +148,8 @@ class HomeKitSpeedMapping:
def speed_to_homekit(self, speed):
"""Map Home Assistant speed state to HomeKit speed."""
+ if speed is None:
+ return None
speed_range = self.speed_ranges[speed]
return speed_range.target
diff --git a/homeassistant/components/homekit_controller/.translations/es-419.json b/homeassistant/components/homekit_controller/.translations/es-419.json
index fb8d63080ce..b058e94e25a 100644
--- a/homeassistant/components/homekit_controller/.translations/es-419.json
+++ b/homeassistant/components/homekit_controller/.translations/es-419.json
@@ -5,6 +5,11 @@
"already_paired": "Este accesorio ya est\u00e1 emparejado con otro dispositivo. Por favor, reinicie el accesorio y vuelva a intentarlo."
},
"step": {
+ "pair": {
+ "data": {
+ "pairing_code": "C\u00f3digo de emparejamiento"
+ }
+ },
"user": {
"data": {
"device": "Dispositivo"
diff --git a/homeassistant/components/homekit_controller/.translations/it.json b/homeassistant/components/homekit_controller/.translations/it.json
new file mode 100644
index 00000000000..6ec1c283448
--- /dev/null
+++ b/homeassistant/components/homekit_controller/.translations/it.json
@@ -0,0 +1,29 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "L'accessorio \u00e8 gi\u00e0 configurato con questo controller."
+ },
+ "error": {
+ "authentication_error": "Codice HomeKit errato. Per favore, controllate e riprovate.",
+ "unable_to_pair": "Impossibile abbinare, per favore riprova.",
+ "unknown_error": "Il dispositivo ha riportato un errore sconosciuto. L'abbinamento non \u00e8 riuscito."
+ },
+ "step": {
+ "pair": {
+ "data": {
+ "pairing_code": "Codice di abbinamento"
+ },
+ "description": "Inserisci il codice di abbinamento HomeKit per usare questo accessorio",
+ "title": "Abbina con accessorio HomeKit"
+ },
+ "user": {
+ "data": {
+ "device": "Dispositivo"
+ },
+ "description": "Selezionare il dispositivo che si desidera abbinare",
+ "title": "Abbina con accessorio HomeKit"
+ }
+ },
+ "title": "Accessorio HomeKit"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/homekit_controller/.translations/ko.json b/homeassistant/components/homekit_controller/.translations/ko.json
index 525604cd96b..b512ad67732 100644
--- a/homeassistant/components/homekit_controller/.translations/ko.json
+++ b/homeassistant/components/homekit_controller/.translations/ko.json
@@ -4,8 +4,8 @@
"already_configured": "\uc561\uc138\uc11c\ub9ac\uac00 \ucee8\ud2b8\ub864\ub7ec\uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"already_paired": "\uc774 \uc561\uc138\uc11c\ub9ac\ub294 \uc774\ubbf8 \ub2e4\ub978 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac\ub97c \uc7ac\uc124\uc815\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.",
"ignored_model": "\uc774 \ubaa8\ub378\uc5d0 \ub300\ud55c HomeKit \uc9c0\uc6d0\uc740 \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc81c\uacf5\ud558\ub294 \uae30\ubcf8 \uad6c\uc131\uc694\uc18c\ub85c \uc778\ud574 \ucc28\ub2e8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
- "invalid_config_entry": "\uc774 \uc7a5\uce58\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant \uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.",
- "no_devices": "\ud398\uc5b4\ub9c1\ub418\uc9c0 \uc54a\uc740 \uc7a5\uce58\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4"
+ "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant \uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.",
+ "no_devices": "\ud398\uc5b4\ub9c1\ub418\uc9c0 \uc54a\uc740 \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4"
},
"error": {
"authentication_error": "HomeKit \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud655\uc778 \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.",
@@ -22,9 +22,9 @@
},
"user": {
"data": {
- "device": "\uc7a5\uce58"
+ "device": "\uae30\uae30"
},
- "description": "\ud398\uc5b4\ub9c1 \ud560 \uc7a5\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694",
+ "description": "\ud398\uc5b4\ub9c1 \ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694",
"title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1"
}
},
diff --git a/homeassistant/components/homekit_controller/.translations/nn.json b/homeassistant/components/homekit_controller/.translations/nn.json
new file mode 100644
index 00000000000..995d6779238
--- /dev/null
+++ b/homeassistant/components/homekit_controller/.translations/nn.json
@@ -0,0 +1,12 @@
+{
+ "config": {
+ "step": {
+ "pair": {
+ "data": {
+ "pairing_code": "Paringskode"
+ }
+ }
+ },
+ "title": "HomeKit tilbeh\u00f8r"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/homekit_controller/.translations/pt.json b/homeassistant/components/homekit_controller/.translations/pt.json
new file mode 100644
index 00000000000..37f68408ce4
--- /dev/null
+++ b/homeassistant/components/homekit_controller/.translations/pt.json
@@ -0,0 +1,16 @@
+{
+ "config": {
+ "step": {
+ "pair": {
+ "data": {
+ "pairing_code": "C\u00f3digo de emparelhamento"
+ }
+ },
+ "user": {
+ "data": {
+ "device": "Dispositivo"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/homekit_controller/.translations/sv.json b/homeassistant/components/homekit_controller/.translations/sv.json
new file mode 100644
index 00000000000..d1453b64938
--- /dev/null
+++ b/homeassistant/components/homekit_controller/.translations/sv.json
@@ -0,0 +1,20 @@
+{
+ "config": {
+ "error": {
+ "unable_to_pair": "Det g\u00e5r inte att para ihop, f\u00f6rs\u00f6k igen."
+ },
+ "step": {
+ "pair": {
+ "title": "Para HomeKit-tillbeh\u00f6r"
+ },
+ "user": {
+ "data": {
+ "device": "Enhet"
+ },
+ "description": "V\u00e4lj den enhet du vill para med",
+ "title": "Para HomeKit-tillbeh\u00f6r"
+ }
+ },
+ "title": "HomeKit-tillbeh\u00f6r"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py
index 2a43d0ac9ce..11026d7e9ac 100644
--- a/homeassistant/components/homekit_controller/__init__.py
+++ b/homeassistant/components/homekit_controller/__init__.py
@@ -12,8 +12,6 @@ from .const import (
)
from .const import DOMAIN # noqa: pylint: disable=unused-import
-REQUIREMENTS = ['homekit[IP]==0.13.0']
-
HOMEKIT_IGNORE = [
'BSB002',
'Home Assistant Bridge',
@@ -186,7 +184,7 @@ def setup(hass, config):
if hkid in hass.data[KNOWN_DEVICES]:
device = hass.data[KNOWN_DEVICES][hkid]
if config_num > device.config_num and \
- device.pairing_info is not None:
+ device.pairing is not None:
device.accessory_setup()
return
diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py
index f9bc25f4237..fe15cfe2eab 100644
--- a/homeassistant/components/homekit_controller/alarm_control_panel.py
+++ b/homeassistant/components/homekit_controller/alarm_control_panel.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
ICON = 'mdi:security'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py
index 2bd03b18932..a5b70082002 100644
--- a/homeassistant/components/homekit_controller/binary_sensor.py
+++ b/homeassistant/components/homekit_controller/binary_sensor.py
@@ -5,8 +5,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py
index 67f1fb72bcf..2cbd8f6d700 100644
--- a/homeassistant/components/homekit_controller/climate.py
+++ b/homeassistant/components/homekit_controller/climate.py
@@ -4,13 +4,11 @@ import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_COOL, STATE_HEAT, STATE_IDLE, SUPPORT_OPERATION_MODE,
- SUPPORT_TARGET_TEMPERATURE)
+ SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY)
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
_LOGGER = logging.getLogger(__name__)
# Map of Homekit operation modes to hass modes
@@ -38,12 +36,14 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def __init__(self, *args):
"""Initialise the device."""
- super().__init__(*args)
self._state = None
self._current_mode = None
self._valid_modes = []
self._current_temp = None
self._target_temp = None
+ self._current_humidity = None
+ self._target_humidity = None
+ super().__init__(*args)
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
@@ -54,20 +54,41 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
CharacteristicsTypes.HEATING_COOLING_TARGET,
CharacteristicsTypes.TEMPERATURE_CURRENT,
CharacteristicsTypes.TEMPERATURE_TARGET,
+ CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT,
+ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET,
]
def _setup_heating_cooling_target(self, characteristic):
self._features |= SUPPORT_OPERATION_MODE
- valid_values = characteristic.get(
- 'valid-values', DEFAULT_VALID_MODES)
+ if 'valid-values' in characteristic:
+ valid_values = [
+ val for val in DEFAULT_VALID_MODES
+ if val in characteristic['valid-values']
+ ]
+ else:
+ valid_values = DEFAULT_VALID_MODES
+ if 'minValue' in characteristic:
+ valid_values = [
+ val for val in valid_values
+ if val >= characteristic['minValue']
+ ]
+ if 'maxValue' in characteristic:
+ valid_values = [
+ val for val in valid_values
+ if val <= characteristic['maxValue']
+ ]
+
self._valid_modes = [
- MODE_HOMEKIT_TO_HASS.get(mode) for mode in valid_values
+ MODE_HOMEKIT_TO_HASS[mode] for mode in valid_values
]
def _setup_temperature_target(self, characteristic):
self._features |= SUPPORT_TARGET_TEMPERATURE
+ def _setup_relative_humidity_target(self, characteristic):
+ self._features |= SUPPORT_TARGET_HUMIDITY
+
def _update_heating_cooling_current(self, value):
self._state = MODE_HOMEKIT_TO_HASS.get(value)
@@ -80,6 +101,12 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def _update_temperature_target(self, value):
self._target_temp = value
+ def _update_relative_humidity_current(self, value):
+ self._current_humidity = value
+
+ def _update_relative_humidity_target(self, value):
+ self._target_humidity = value
+
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
@@ -89,6 +116,13 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
'value': temp}]
await self._accessory.put_characteristics(characteristics)
+ async def async_set_humidity(self, humidity):
+ """Set new target humidity."""
+ characteristics = [{'aid': self._aid,
+ 'iid': self._chars['relative-humidity.target'],
+ 'value': humidity}]
+ await self._accessory.put_characteristics(characteristics)
+
async def async_set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
characteristics = [{'aid': self._aid,
@@ -118,6 +152,16 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temp
+ @property
+ def current_humidity(self):
+ """Return the current humidity."""
+ return self._current_humidity
+
+ @property
+ def target_humidity(self):
+ """Return the humidity we try to reach."""
+ return self._target_humidity
+
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py
index 2ca568b547f..032032d30ab 100644
--- a/homeassistant/components/homekit_controller/connection.py
+++ b/homeassistant/components/homekit_controller/connection.py
@@ -145,9 +145,14 @@ class HKDevice():
self.pairing = self.controller.pairings.get(self.hkid)
if self.pairing is not None:
- pairing_file = os.path.join(
+ pairing_dir = os.path.join(
self.hass.config.path(),
HOMEKIT_DIR,
+ )
+ if not os.path.exists(pairing_dir):
+ os.makedirs(pairing_dir)
+ pairing_file = os.path.join(
+ pairing_dir,
PAIRING_FILE,
)
self.controller.save_data(pairing_file)
diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py
index 26b7613ed2b..bd466d074d0 100644
--- a/homeassistant/components/homekit_controller/cover.py
+++ b/homeassistant/components/homekit_controller/cover.py
@@ -3,7 +3,7 @@ import logging
from homeassistant.components.cover import (
ATTR_POSITION, ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT,
- SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION,
+ SUPPORT_OPEN, SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, SUPPORT_STOP,
SUPPORT_SET_TILT_POSITION, CoverDevice)
from homeassistant.const import (
STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING)
@@ -12,8 +12,6 @@ from . import KNOWN_DEVICES, HomeKitEntity
STATE_STOPPED = 'stopped'
-DEPENDENCIES = ['homekit_controller']
-
_LOGGER = logging.getLogger(__name__)
CURRENT_GARAGE_STATE_MAP = {
@@ -137,9 +135,10 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
self._state = None
self._position = None
self._tilt_position = None
- self._hold = None
self._obstruction_detected = None
self.lock_state = None
+ self._features = (
+ SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION)
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
@@ -157,15 +156,25 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
CharacteristicsTypes.OBSTRUCTION_DETECTED,
]
+ def _setup_position_hold(self, char):
+ self._features |= SUPPORT_STOP
+
+ def _setup_vertical_tilt_current(self, char):
+ self._features |= (
+ SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT |
+ SUPPORT_SET_TILT_POSITION)
+
+ def _setup_horizontal_tilt_current(self, char):
+ self._features |= (
+ SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT |
+ SUPPORT_SET_TILT_POSITION)
+
def _update_position_state(self, value):
self._state = CURRENT_WINDOW_STATE_MAP[value]
def _update_position_current(self, value):
self._position = value
- def _update_position_hold(self, value):
- self._hold = value
-
def _update_vertical_tilt_current(self, value):
self._tilt_position = value
@@ -175,21 +184,10 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
def _update_obstruction_detected(self, value):
self._obstruction_detected = value
- def _update_name(self, value):
- self._hold = value
-
@property
def supported_features(self):
"""Flag supported features."""
- supported_features = (
- SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION)
-
- if self._tilt_position is not None:
- supported_features |= (
- SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT |
- SUPPORT_SET_TILT_POSITION)
-
- return supported_features
+ return self._features
@property
def current_cover_position(self):
@@ -211,6 +209,13 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
"""Return if the cover is opening or not."""
return self._state == STATE_OPENING
+ async def async_stop_cover(self, **kwargs):
+ """Send hold command."""
+ characteristics = [{'aid': self._aid,
+ 'iid': self._chars['position.hold'],
+ 'value': 1}]
+ await self._accessory.put_characteristics(characteristics)
+
async def async_open_cover(self, **kwargs):
"""Send open command."""
await self.async_set_cover_position(position=100)
@@ -255,8 +260,4 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
state_attributes['obstruction-detected'] = \
self._obstruction_detected
- if self._hold is not None:
- state_attributes['hold-position'] = \
- self._hold
-
return state_attributes
diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py
index cb9259df4a9..a139b1f2932 100644
--- a/homeassistant/components/homekit_controller/light.py
+++ b/homeassistant/components/homekit_controller/light.py
@@ -7,8 +7,6 @@ from homeassistant.components.light import (
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py
index 0d0275fda16..67de2bfaf3f 100644
--- a/homeassistant/components/homekit_controller/lock.py
+++ b/homeassistant/components/homekit_controller/lock.py
@@ -7,8 +7,6 @@ from homeassistant.const import (
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
_LOGGER = logging.getLogger(__name__)
STATE_JAMMED = 'jammed'
diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json
new file mode 100644
index 00000000000..e724f680b60
--- /dev/null
+++ b/homeassistant/components/homekit_controller/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "homekit_controller",
+ "name": "Homekit controller",
+ "documentation": "https://www.home-assistant.io/components/homekit_controller",
+ "requirements": [
+ "homekit[IP]==0.13.0"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py
index 8cbc8f248ba..b377da80142 100644
--- a/homeassistant/components/homekit_controller/sensor.py
+++ b/homeassistant/components/homekit_controller/sensor.py
@@ -3,8 +3,6 @@ from homeassistant.const import TEMP_CELSIUS
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
HUMIDITY_ICON = 'mdi-water-percent'
TEMP_C_ICON = "mdi-temperature-celsius"
BRIGHTNESS_ICON = "mdi-brightness-6"
diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py
index 34e83c06526..c09502373a6 100644
--- a/homeassistant/components/homekit_controller/switch.py
+++ b/homeassistant/components/homekit_controller/switch.py
@@ -5,8 +5,6 @@ from homeassistant.components.switch import SwitchDevice
from . import KNOWN_DEVICES, HomeKitEntity
-DEPENDENCIES = ['homekit_controller']
-
OUTLET_IN_USE = "outlet_in_use"
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py
index a8109af5ed8..747b23bb970 100644
--- a/homeassistant/components/homematic/__init__.py
+++ b/homeassistant/components/homematic/__init__.py
@@ -14,8 +14,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyhomematic==0.1.58']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'homematic'
@@ -125,7 +123,8 @@ HM_ATTRIBUTE_SUPPORT = {
'CURRENT': ['current', {}],
'VOLTAGE': ['voltage', {}],
'OPERATING_VOLTAGE': ['voltage', {}],
- 'WORKING': ['working', {0: 'No', 1: 'Yes'}]
+ 'WORKING': ['working', {0: 'No', 1: 'Yes'}],
+ 'STATE_UNCERTAIN': ['state_uncertain', {}]
}
HM_PRESS_EVENTS = [
diff --git a/homeassistant/components/homematic/binary_sensor.py b/homeassistant/components/homematic/binary_sensor.py
index 7bf260a9bdc..dfd7b7a72bd 100644
--- a/homeassistant/components/homematic/binary_sensor.py
+++ b/homeassistant/components/homematic/binary_sensor.py
@@ -8,8 +8,6 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematic']
-
SENSOR_TYPES_CLASS = {
'IPShutterContact': 'opening',
'MaxShutterContact': 'opening',
diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py
index 146cad1bc4c..e10d486b727 100644
--- a/homeassistant/components/homematic/climate.py
+++ b/homeassistant/components/homematic/climate.py
@@ -9,8 +9,6 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice
-DEPENDENCIES = ['homematic']
-
_LOGGER = logging.getLogger(__name__)
STATE_BOOST = 'boost'
diff --git a/homeassistant/components/homematic/cover.py b/homeassistant/components/homematic/cover.py
index 33b764dc31f..387eb26f433 100644
--- a/homeassistant/components/homematic/cover.py
+++ b/homeassistant/components/homematic/cover.py
@@ -9,8 +9,6 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematic']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the platform."""
diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py
index c3601461173..f9bc785d3f4 100644
--- a/homeassistant/components/homematic/light.py
+++ b/homeassistant/components/homematic/light.py
@@ -2,15 +2,13 @@
import logging
from homeassistant.components.light import (
- ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS,
- SUPPORT_COLOR, SUPPORT_EFFECT, Light)
+ ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_TRANSITION,
+ SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, Light)
from . import ATTR_DISCOVER_DEVICES, HMDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematic']
-
SUPPORT_HOMEMATIC = SUPPORT_BRIGHTNESS
@@ -77,6 +75,9 @@ class HMLight(HMDevice, Light):
def turn_on(self, **kwargs):
"""Turn the light on and/or change color or color effect settings."""
+ if ATTR_TRANSITION in kwargs:
+ self._hmdevice.setValue('RAMP_TIME', kwargs[ATTR_TRANSITION])
+
if ATTR_BRIGHTNESS in kwargs and self._state == "LEVEL":
percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255
self._hmdevice.set_level(percent_bright, self._channel)
diff --git a/homeassistant/components/homematic/lock.py b/homeassistant/components/homematic/lock.py
index 3c0ca040c5f..7f796b32885 100644
--- a/homeassistant/components/homematic/lock.py
+++ b/homeassistant/components/homematic/lock.py
@@ -8,8 +8,6 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematic']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Homematic lock platform."""
diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json
new file mode 100644
index 00000000000..7c80806cae5
--- /dev/null
+++ b/homeassistant/components/homematic/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "homematic",
+ "name": "Homematic",
+ "documentation": "https://www.home-assistant.io/components/homematic",
+ "requirements": [
+ "pyhomematic==0.1.58"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@pvizeli",
+ "@danielperna84"
+ ]
+}
diff --git a/homeassistant/components/homematic/notify.py b/homeassistant/components/homematic/notify.py
index 021560eee3c..74ea7095b41 100644
--- a/homeassistant/components/homematic/notify.py
+++ b/homeassistant/components/homematic/notify.py
@@ -1,9 +1,4 @@
-"""
-Notification support for Homematic.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.homematic/
-"""
+"""Notification support for Homematic."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from . import (
SERVICE_SET_DEVICE_VALUE)
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ["homematic"]
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(ATTR_ADDRESS): vol.All(cv.string, vol.Upper),
vol.Required(ATTR_CHANNEL): vol.Coerce(int),
diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py
index 401d11f70c8..fca8c746a49 100644
--- a/homeassistant/components/homematic/sensor.py
+++ b/homeassistant/components/homematic/sensor.py
@@ -7,8 +7,6 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematic']
-
HM_STATE_HA_CAST = {
'RotaryHandleSensor': {0: 'closed', 1: 'tilted', 2: 'open'},
'RotaryHandleSensorIP': {0: 'closed', 1: 'tilted', 2: 'open'},
diff --git a/homeassistant/components/homematic/switch.py b/homeassistant/components/homematic/switch.py
index 393ad09b310..b77b3a1f700 100644
--- a/homeassistant/components/homematic/switch.py
+++ b/homeassistant/components/homematic/switch.py
@@ -8,8 +8,6 @@ from . import ATTR_DISCOVER_DEVICES, HMDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematic']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the HomeMatic switch platform."""
diff --git a/homeassistant/components/homematicip_cloud/.translations/ko.json b/homeassistant/components/homematicip_cloud/.translations/ko.json
index b60da944f64..2f47fcddf28 100644
--- a/homeassistant/components/homematicip_cloud/.translations/ko.json
+++ b/homeassistant/components/homematicip_cloud/.translations/ko.json
@@ -15,14 +15,14 @@
"init": {
"data": {
"hapid": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 ID (SGTIN)",
- "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uc7a5\uce58 \uc774\ub984\uc758 \uc811\ub450\uc5b4\ub85c \uc0ac\uc6a9)",
+ "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc5b4\ub85c \uc0ac\uc6a9)",
"pin": "PIN \ucf54\ub4dc (\uc120\ud0dd\uc0ac\ud56d)"
},
"title": "HomematicIP \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc120\ud0dd"
},
"link": {
"description": "Home Assistant \uc5d0 HomematicIP \ub97c \ub4f1\ub85d\ud558\ub824\uba74 \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc758 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uacfc Submit \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.\n\n",
- "title": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc5d0 \uc5f0\uacb0"
+ "title": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc5f0\uacb0"
}
},
"title": "HomematicIP \ud074\ub77c\uc6b0\ub4dc"
diff --git a/homeassistant/components/homematicip_cloud/.translations/nn.json b/homeassistant/components/homematicip_cloud/.translations/nn.json
index 966c827c89d..da375563d91 100644
--- a/homeassistant/components/homematicip_cloud/.translations/nn.json
+++ b/homeassistant/components/homematicip_cloud/.translations/nn.json
@@ -21,7 +21,7 @@
"title": "Vel HomematicIP tilgangspunkt"
},
"link": {
- "description": "Trykk p\u00e5 den bl\u00e5 knappen p\u00e5 tilgangspunktet og sendknappen for \u00e5 registrere HomematicIP med Home Assitant.\n\n ! [Plassering av knapp p\u00e5 bro] (/ static / images / config_flows / config_homematicip_cloud.png)",
+ "description": "Trykk p\u00e5 den bl\u00e5 knappen p\u00e5 tilgangspunktet og sendknappen for \u00e5 registrere HomematicIP med Home Assistant.\n\n ! [Plassering av knapp p\u00e5 bro] (/ static / images / config_flows / config_homematicip_cloud.png)",
"title": "Link tilgangspunk"
}
},
diff --git a/homeassistant/components/homematicip_cloud/.translations/no.json b/homeassistant/components/homematicip_cloud/.translations/no.json
index d9e6636c972..28cfc502aba 100644
--- a/homeassistant/components/homematicip_cloud/.translations/no.json
+++ b/homeassistant/components/homematicip_cloud/.translations/no.json
@@ -25,6 +25,6 @@
"title": "Link tilgangspunkt"
}
},
- "title": "HomematicIP Sky"
+ "title": "HomematicIP Cloud"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/homematicip_cloud/.translations/pt.json b/homeassistant/components/homematicip_cloud/.translations/pt.json
index 8b431125ef0..0954f3ff4f9 100644
--- a/homeassistant/components/homematicip_cloud/.translations/pt.json
+++ b/homeassistant/components/homematicip_cloud/.translations/pt.json
@@ -21,7 +21,7 @@
"title": "Escolher ponto de acesso HomematicIP"
},
"link": {
- "description": "Pressione o bot\u00e3o azul no ponto de acesso e o bot\u00e3o enviar para registrar HomematicIP com o Home Assistant.\n\n",
+ "description": "Pressione o bot\u00e3o azul no ponto de acesso e o bot\u00e3o enviar para registrar HomematicIP com o Home Assistant.\n\n",
"title": "Associar ponto de acesso"
}
},
diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py
index ac93ef05b85..4a24120be95 100644
--- a/homeassistant/components/homematicip_cloud/__init__.py
+++ b/homeassistant/components/homematicip_cloud/__init__.py
@@ -15,8 +15,6 @@ from .const import (
from .device import HomematicipGenericDevice # noqa: F401
from .hap import HomematicipAuth, HomematicipHAP # noqa: F401
-REQUIREMENTS = ['homematicip==0.10.6']
-
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py
index eb5855bb980..cb35833c231 100644
--- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py
+++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py
@@ -10,11 +10,6 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematicip_cloud']
-
-HMIP_ZONE_AWAY = 'EXTERNAL'
-HMIP_ZONE_HOME = 'INTERNAL'
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py
index 786a28a70a5..48e9520a952 100644
--- a/homeassistant/components/homematicip_cloud/binary_sensor.py
+++ b/homeassistant/components/homematicip_cloud/binary_sensor.py
@@ -6,8 +6,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
from .device import ATTR_GROUP_MEMBER_UNREACHABLE
-DEPENDENCIES = ['homematicip_cloud']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MOTIONDETECTED = 'motion detected'
@@ -29,10 +27,10 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the HomematicIP Cloud binary sensor from a config entry."""
from homematicip.aio.device import (
- AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector,
- AsyncWaterSensor, AsyncRotaryHandleSensor,
- AsyncMotionDetectorPushButton, AsyncWeatherSensor,
- AsyncWeatherSensorPlus, AsyncWeatherSensorPro)
+ AsyncDevice, AsyncShutterContact, AsyncMotionDetectorIndoor,
+ AsyncMotionDetectorOutdoor, AsyncSmokeDetector, AsyncWaterSensor,
+ AsyncRotaryHandleSensor, AsyncMotionDetectorPushButton,
+ AsyncWeatherSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro)
from homematicip.aio.group import (
AsyncSecurityGroup, AsyncSecurityZoneGroup)
@@ -43,6 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)):
devices.append(HomematicipShutterContact(home, device))
if isinstance(device, (AsyncMotionDetectorIndoor,
+ AsyncMotionDetectorOutdoor,
AsyncMotionDetectorPushButton)):
devices.append(HomematicipMotionDetector(home, device))
if isinstance(device, AsyncSmokeDetector):
@@ -56,6 +55,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
AsyncWeatherSensorPro)):
devices.append(HomematicipStormSensor(home, device))
devices.append(HomematicipSunshineSensor(home, device))
+ if isinstance(device, AsyncDevice) and device.lowBat is not None:
+ devices.append(HomematicipBatterySensor(home, device))
for group in home.groups:
if isinstance(group, AsyncSecurityGroup):
@@ -197,6 +198,24 @@ class HomematicipSunshineSensor(HomematicipGenericDevice, BinarySensorDevice):
return attr
+class HomematicipBatterySensor(HomematicipGenericDevice, BinarySensorDevice):
+ """Representation of a HomematicIP Cloud low battery sensor."""
+
+ def __init__(self, home, device):
+ """Initialize battery sensor."""
+ super().__init__(home, device, 'Battery')
+
+ @property
+ def device_class(self):
+ """Return the class of this sensor."""
+ return 'battery'
+
+ @property
+ def is_on(self):
+ """Return true if battery is low."""
+ return self._device.lowBat
+
+
class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice,
BinarySensorDevice):
"""Representation of a HomematicIP Cloud security zone group."""
diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py
index 955f3e5baa7..5055858e9c7 100644
--- a/homeassistant/components/homematicip_cloud/climate.py
+++ b/homeassistant/components/homematicip_cloud/climate.py
@@ -10,8 +10,6 @@ from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
_LOGGER = logging.getLogger(__name__)
-STATE_BOOST = 'Boost'
-
HA_STATE_TO_HMIP = {
STATE_AUTO: 'AUTOMATIC',
STATE_MANUAL: 'MANUAL',
diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py
index 735e8789670..e572e3d9754 100644
--- a/homeassistant/components/homematicip_cloud/cover.py
+++ b/homeassistant/components/homematicip_cloud/cover.py
@@ -5,8 +5,6 @@ from homeassistant.components.cover import ATTR_POSITION, CoverDevice
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
-DEPENDENCIES = ['homematicip_cloud']
-
_LOGGER = logging.getLogger(__name__)
HMIP_COVER_OPEN = 0
diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py
index f8b19b5bb1e..b67e4114db2 100644
--- a/homeassistant/components/homematicip_cloud/light.py
+++ b/homeassistant/components/homematicip_cloud/light.py
@@ -7,13 +7,10 @@ from homeassistant.components.light import (
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
-DEPENDENCIES = ['homematicip_cloud']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ENERGY_COUNTER = 'energy_counter_kwh'
ATTR_POWER_CONSUMPTION = 'power_consumption'
-ATTR_PROFILE_MODE = 'profile_mode'
async def async_setup_platform(
@@ -77,13 +74,9 @@ class HomematicipLightMeasuring(HomematicipLight):
"""Return the state attributes of the generic device."""
attr = super().device_state_attributes
if self._device.currentPowerConsumption > 0.05:
- attr.update({
- ATTR_POWER_CONSUMPTION:
- round(self._device.currentPowerConsumption, 2)
- })
- attr.update({
- ATTR_ENERGY_COUNTER: round(self._device.energyCounter, 2)
- })
+ attr[ATTR_POWER_CONSUMPTION] = \
+ round(self._device.currentPowerConsumption, 2)
+ attr[ATTR_ENERGY_COUNTER] = round(self._device.energyCounter, 2)
return attr
@@ -168,10 +161,7 @@ class HomematicipNotificationLight(HomematicipGenericDevice, Light):
"""Return the state attributes of the generic device."""
attr = super().device_state_attributes
if self.is_on:
- attr.update({
- ATTR_COLOR_NAME:
- self._channel.simpleRGBColorState
- })
+ attr[ATTR_COLOR_NAME] = self._channel.simpleRGBColorState
return attr
@property
diff --git a/homeassistant/components/homematicip_cloud/manifest.json b/homeassistant/components/homematicip_cloud/manifest.json
new file mode 100644
index 00000000000..030b4d5b79b
--- /dev/null
+++ b/homeassistant/components/homematicip_cloud/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "homematicip_cloud",
+ "name": "Homematicip cloud",
+ "documentation": "https://www.home-assistant.io/components/homematicip_cloud",
+ "requirements": [
+ "homematicip==0.10.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py
index 39758739400..201a5be6c51 100644
--- a/homeassistant/components/homematicip_cloud/sensor.py
+++ b/homeassistant/components/homematicip_cloud/sensor.py
@@ -2,18 +2,14 @@
import logging
from homeassistant.const import (
- DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE,
- POWER_WATT, TEMP_CELSIUS)
+ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER,
+ DEVICE_CLASS_TEMPERATURE, POWER_WATT, TEMP_CELSIUS)
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['homematicip_cloud']
-
ATTR_TEMPERATURE_OFFSET = 'temperature_offset'
-ATTR_VALVE_STATE = 'valve_state'
-ATTR_VALVE_POSITION = 'valve_position'
ATTR_WIND_DIRECTION = 'wind_direction'
ATTR_WIND_DIRECTION_VARIATION = 'wind_direction_variation_in_degree'
@@ -30,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
AsyncHeatingThermostat, AsyncHeatingThermostatCompact,
AsyncTemperatureHumiditySensorWithoutDisplay,
AsyncTemperatureHumiditySensorDisplay, AsyncMotionDetectorIndoor,
- AsyncTemperatureHumiditySensorOutdoor,
+ AsyncMotionDetectorOutdoor, AsyncTemperatureHumiditySensorOutdoor,
AsyncMotionDetectorPushButton, AsyncLightSensor,
AsyncPlugableSwitchMeasuring, AsyncBrandSwitchMeasuring,
AsyncFullFlushSwitchMeasuring, AsyncWeatherSensor,
@@ -51,6 +47,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
devices.append(HomematicipTemperatureSensor(home, device))
devices.append(HomematicipHumiditySensor(home, device))
if isinstance(device, (AsyncMotionDetectorIndoor,
+ AsyncMotionDetectorOutdoor,
AsyncMotionDetectorPushButton,
AsyncWeatherSensor,
AsyncWeatherSensorPlus,
@@ -240,6 +237,11 @@ class HomematicipPowerSensor(HomematicipGenericDevice):
"""Initialize the device."""
super().__init__(home, device, 'Power')
+ @property
+ def device_class(self):
+ """Return the device class of the sensor."""
+ return DEVICE_CLASS_POWER
+
@property
def state(self):
"""Represenation of the HomematicIP power comsumption value."""
diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py
index 62e72f0ade7..b96e0c4cf4d 100644
--- a/homeassistant/components/homematicip_cloud/switch.py
+++ b/homeassistant/components/homematicip_cloud/switch.py
@@ -6,14 +6,8 @@ from homeassistant.components.switch import SwitchDevice
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
from .device import ATTR_GROUP_MEMBER_UNREACHABLE
-DEPENDENCIES = ['homematicip_cloud']
-
_LOGGER = logging.getLogger(__name__)
-ATTR_POWER_CONSUMPTION = 'power_consumption'
-ATTR_ENERGIE_COUNTER = 'energie_counter'
-ATTR_PROFILE_MODE = 'profile_mode'
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@@ -29,6 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
AsyncBrandSwitchMeasuring,
AsyncFullFlushSwitchMeasuring,
AsyncOpenCollector8Module,
+ AsyncMultiIOBox,
)
from homematicip.aio.group import AsyncSwitchingGroup
@@ -49,6 +44,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
elif isinstance(device, AsyncOpenCollector8Module):
for channel in range(1, 9):
devices.append(HomematicipMultiSwitch(home, device, channel))
+ elif isinstance(device, AsyncMultiIOBox):
+ for channel in range(1, 3):
+ devices.append(HomematicipMultiSwitch(home, device, channel))
for group in home.groups:
if isinstance(group, AsyncSwitchingGroup):
diff --git a/homeassistant/components/homematicip_cloud/weather.py b/homeassistant/components/homematicip_cloud/weather.py
index 101adcdeaaa..74b302b18fc 100644
--- a/homeassistant/components/homematicip_cloud/weather.py
+++ b/homeassistant/components/homematicip_cloud/weather.py
@@ -3,11 +3,10 @@
import logging
from homeassistant.components.weather import WeatherEntity
+from homeassistant.const import TEMP_CELSIUS
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice
-DEPENDENCIES = ['homematicip_cloud']
-
_LOGGER = logging.getLogger(__name__)
@@ -55,7 +54,7 @@ class HomematicipWeatherSensor(HomematicipGenericDevice, WeatherEntity):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
- return self.hass.config.units.temperature_unit
+ return TEMP_CELSIUS
@property
def humidity(self):
diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py
index d0769ed25e6..b722a5a4a2d 100644
--- a/homeassistant/components/homeworks/__init__.py
+++ b/homeassistant/components/homeworks/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.util import slugify
-REQUIREMENTS = ['pyhomeworks==0.0.6']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'homeworks'
diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py
index ca41dff9834..710be7c0077 100644
--- a/homeassistant/components/homeworks/light.py
+++ b/homeassistant/components/homeworks/light.py
@@ -11,8 +11,6 @@ from . import (
CONF_ADDR, CONF_DIMMERS, CONF_RATE, ENTITY_SIGNAL, HOMEWORKS_CONTROLLER,
HomeworksDevice)
-DEPENDENCIES = ['homeworks']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/homeworks/manifest.json b/homeassistant/components/homeworks/manifest.json
new file mode 100644
index 00000000000..cdbbffb8d36
--- /dev/null
+++ b/homeassistant/components/homeworks/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "homeworks",
+ "name": "Homeworks",
+ "documentation": "https://www.home-assistant.io/components/homeworks",
+ "requirements": [
+ "pyhomeworks==0.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py
index a76f992a76a..df19f67a876 100644
--- a/homeassistant/components/honeywell/climate.py
+++ b/homeassistant/components/honeywell/climate.py
@@ -1,11 +1,5 @@
-"""
-Support for Honeywell Round Connected and Honeywell Evohome thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.honeywell/
-"""
+"""Support for Honeywell Round Connected and Honeywell Evohome thermostats."""
import logging
-import socket
import datetime
import requests
@@ -21,8 +15,6 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE, CONF_REGION)
-REQUIREMENTS = ['evohomeclient==0.2.8', 'somecomfort==0.5.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
@@ -78,9 +70,10 @@ def _setup_round(username, password, config, add_entities):
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)],
True
)
- except socket.error:
+ except requests.exceptions.RequestException as err:
_LOGGER.error(
- "Connection error logging into the honeywell evohome web service")
+ "Connection error logging into the honeywell evohome web service, "
+ "hint: %s", err)
return False
return True
diff --git a/homeassistant/components/honeywell/manifest.json b/homeassistant/components/honeywell/manifest.json
new file mode 100644
index 00000000000..c3d76703e91
--- /dev/null
+++ b/homeassistant/components/honeywell/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "honeywell",
+ "name": "Honeywell",
+ "documentation": "https://www.home-assistant.io/components/honeywell",
+ "requirements": [
+ "evohomeclient==0.3.2",
+ "somecomfort==0.5.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hook/manifest.json b/homeassistant/components/hook/manifest.json
new file mode 100644
index 00000000000..d9898a71f8b
--- /dev/null
+++ b/homeassistant/components/hook/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "hook",
+ "name": "Hook",
+ "documentation": "https://www.home-assistant.io/components/hook",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hook/switch.py b/homeassistant/components/hook/switch.py
index fdc13f59d28..7a11c1dd8b7 100644
--- a/homeassistant/components/hook/switch.py
+++ b/homeassistant/components/hook/switch.py
@@ -1,9 +1,4 @@
-"""
-Support Hook, available at hooksmarthome.com.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.hook/
-"""
+"""Support Hook, available at hooksmarthome.com."""
import logging
import asyncio
diff --git a/homeassistant/components/horizon/manifest.json b/homeassistant/components/horizon/manifest.json
new file mode 100644
index 00000000000..2916e81ce4f
--- /dev/null
+++ b/homeassistant/components/horizon/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "horizon",
+ "name": "Horizon",
+ "documentation": "https://www.home-assistant.io/components/horizon",
+ "requirements": [
+ "horimote==0.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py
index 3b1ae36152d..ab72b051f1b 100644
--- a/homeassistant/components/horizon/media_player.py
+++ b/homeassistant/components/horizon/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for the Unitymedia Horizon HD Recorder.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/media_player.horizon/
-"""
+"""Support for the Unitymedia Horizon HD Recorder."""
from datetime import timedelta
import logging
@@ -21,8 +16,6 @@ from homeassistant.const import (
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['horimote==0.4.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Horizon'
diff --git a/homeassistant/components/hp_ilo/manifest.json b/homeassistant/components/hp_ilo/manifest.json
new file mode 100644
index 00000000000..3df6632e47a
--- /dev/null
+++ b/homeassistant/components/hp_ilo/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hp_ilo",
+ "name": "Hp ilo",
+ "documentation": "https://www.home-assistant.io/components/hp_ilo",
+ "requirements": [
+ "python-hpilo==3.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py
index a017f0ee3e8..46fde885613 100644
--- a/homeassistant/components/hp_ilo/sensor.py
+++ b/homeassistant/components/hp_ilo/sensor.py
@@ -13,8 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['python-hpilo==3.9']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "HP ILO"
diff --git a/homeassistant/components/html5/manifest.json b/homeassistant/components/html5/manifest.json
new file mode 100644
index 00000000000..7b43ec44ef3
--- /dev/null
+++ b/homeassistant/components/html5/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "html5",
+ "name": "HTML5 Notifications",
+ "documentation": "https://www.home-assistant.io/components/html5",
+ "requirements": [
+ "pywebpush==1.9.2"
+ ],
+ "dependencies": ["frontend"],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py
index 8744d0afb28..2fcaa266b85 100644
--- a/homeassistant/components/html5/notify.py
+++ b/homeassistant/components/html5/notify.py
@@ -1,9 +1,4 @@
-"""
-HTML5 Push Messaging notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.html5/
-"""
+"""HTML5 Push Messaging notification service."""
import datetime
from functools import partial
import json
@@ -29,10 +24,6 @@ from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, DOMAIN,
PLATFORM_SCHEMA, BaseNotificationService)
-REQUIREMENTS = ['pywebpush==1.6.0']
-
-DEPENDENCIES = ['frontend']
-
_LOGGER = logging.getLogger(__name__)
REGISTRATIONS_FILE = 'html5_push_registrations.conf'
diff --git a/homeassistant/components/html5/services.yaml b/homeassistant/components/html5/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py
index 0bcf3f85ff7..ad64b38200a 100644
--- a/homeassistant/components/http/__init__.py
+++ b/homeassistant/components/http/__init__.py
@@ -29,8 +29,6 @@ from .real_ip import setup_real_ip
from .static import CACHE_HEADERS, CachingStaticResource
from .view import HomeAssistantView # noqa
-REQUIREMENTS = ['aiohttp_cors==0.7.0']
-
DOMAIN = 'http'
CONF_API_PASSWORD = 'api_password'
diff --git a/homeassistant/components/http/manifest.json b/homeassistant/components/http/manifest.json
new file mode 100644
index 00000000000..0bc5586445d
--- /dev/null
+++ b/homeassistant/components/http/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "http",
+ "name": "HTTP",
+ "documentation": "https://www.home-assistant.io/components/http",
+ "requirements": [
+ "aiohttp_cors==0.7.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/htu21d/manifest.json b/homeassistant/components/htu21d/manifest.json
new file mode 100644
index 00000000000..70093df9b55
--- /dev/null
+++ b/homeassistant/components/htu21d/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "htu21d",
+ "name": "Htu21d",
+ "documentation": "https://www.home-assistant.io/components/htu21d",
+ "requirements": [
+ "i2csense==0.0.4",
+ "smbus-cffi==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/htu21d/sensor.py b/homeassistant/components/htu21d/sensor.py
index 4f8665b2011..01c2b0399b9 100644
--- a/homeassistant/components/htu21d/sensor.py
+++ b/homeassistant/components/htu21d/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for HTU21D temperature and humidity sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.htu21d/
-"""
+"""Support for HTU21D temperature and humidity sensor."""
from datetime import timedelta
from functools import partial
import logging
@@ -17,9 +12,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from homeassistant.util.temperature import celsius_to_fahrenheit
-REQUIREMENTS = ['i2csense==0.0.4',
- 'smbus-cffi==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_I2C_BUS = 'i2c_bus'
diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py
index a462b1b3072..8e401dfd239 100644
--- a/homeassistant/components/huawei_lte/__init__.py
+++ b/homeassistant/components/huawei_lte/__init__.py
@@ -19,8 +19,6 @@ _LOGGER = logging.getLogger(__name__)
# https://github.com/quandyfactory/dicttoxml/issues/60
logging.getLogger('dicttoxml').setLevel(logging.WARNING)
-REQUIREMENTS = ['huawei-lte-api==1.1.5']
-
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
DOMAIN = 'huawei_lte'
diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py
index 69bf42fb3fe..d6c49f5e255 100644
--- a/homeassistant/components/huawei_lte/device_tracker.py
+++ b/homeassistant/components/huawei_lte/device_tracker.py
@@ -11,8 +11,6 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_URL
from ..huawei_lte import DATA_KEY, RouterData
-DEPENDENCIES = ['huawei_lte']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_URL): cv.url,
})
diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json
new file mode 100644
index 00000000000..2e096343b09
--- /dev/null
+++ b/homeassistant/components/huawei_lte/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "huawei_lte",
+ "name": "Huawei lte",
+ "documentation": "https://www.home-assistant.io/components/huawei_lte",
+ "requirements": [
+ "huawei-lte-api==1.1.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@scop"
+ ]
+}
diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py
index 5e20a774c25..6394140c07f 100644
--- a/homeassistant/components/huawei_lte/notify.py
+++ b/homeassistant/components/huawei_lte/notify.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from ..huawei_lte import DATA_KEY
-DEPENDENCIES = ['huawei_lte']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py
index 42ad4b52f8d..42bd1f16271 100644
--- a/homeassistant/components/huawei_lte/sensor.py
+++ b/homeassistant/components/huawei_lte/sensor.py
@@ -16,8 +16,6 @@ from ..huawei_lte import DATA_KEY, RouterData
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['huawei_lte']
-
DEFAULT_NAME_TEMPLATE = 'Huawei {} {}'
DEFAULT_SENSORS = [
diff --git a/homeassistant/components/huawei_router/device_tracker.py b/homeassistant/components/huawei_router/device_tracker.py
index 18f3c0b8c62..88e2a57a579 100644
--- a/homeassistant/components/huawei_router/device_tracker.py
+++ b/homeassistant/components/huawei_router/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for HUAWEI routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.huawei_router/
-"""
+"""Support for HUAWEI routers."""
import base64
import logging
import re
diff --git a/homeassistant/components/huawei_router/manifest.json b/homeassistant/components/huawei_router/manifest.json
new file mode 100644
index 00000000000..54fd155b557
--- /dev/null
+++ b/homeassistant/components/huawei_router/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "huawei_router",
+ "name": "Huawei router",
+ "documentation": "https://www.home-assistant.io/components/huawei_router",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@abmantis"
+ ]
+}
diff --git a/homeassistant/components/hue/.translations/th.json b/homeassistant/components/hue/.translations/th.json
index 0b91783f804..c76064c0ab6 100644
--- a/homeassistant/components/hue/.translations/th.json
+++ b/homeassistant/components/hue/.translations/th.json
@@ -1,5 +1,11 @@
{
"config": {
+ "abort": {
+ "unknown": "\u0e40\u0e01\u0e34\u0e14\u0e02\u0e49\u0e2d\u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14\u0e17\u0e35\u0e48\u0e44\u0e21\u0e48\u0e17\u0e23\u0e32\u0e1a\u0e2a\u0e32\u0e40\u0e2b\u0e15\u0e38"
+ },
+ "error": {
+ "register_failed": "\u0e01\u0e32\u0e23\u0e25\u0e07\u0e17\u0e30\u0e40\u0e1a\u0e35\u0e22\u0e19\u0e25\u0e49\u0e21\u0e40\u0e2b\u0e25\u0e27\u0e42\u0e1b\u0e23\u0e14\u0e25\u0e2d\u0e07\u0e2d\u0e35\u0e01\u0e04\u0e23\u0e31\u0e49\u0e07"
+ },
"title": "Philips Hue"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py
index 8f5c27f6516..ac17e6e852f 100644
--- a/homeassistant/components/hue/__init__.py
+++ b/homeassistant/components/hue/__init__.py
@@ -14,8 +14,6 @@ from .bridge import HueBridge
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
-REQUIREMENTS = ['aiohue==1.9.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_BRIDGES = "bridges"
diff --git a/homeassistant/components/hue/binary_sensor.py b/homeassistant/components/hue/binary_sensor.py
new file mode 100644
index 00000000000..b9921a9a01f
--- /dev/null
+++ b/homeassistant/components/hue/binary_sensor.py
@@ -0,0 +1,28 @@
+"""Hue binary sensor entities."""
+from homeassistant.components.binary_sensor import (
+ BinarySensorDevice, DEVICE_CLASS_MOTION)
+from homeassistant.components.hue.sensor_base import (
+ GenericZLLSensor, async_setup_entry as shared_async_setup_entry)
+
+
+PRESENCE_NAME_FORMAT = "{} motion"
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Defer binary sensor setup to the shared sensor module."""
+ await shared_async_setup_entry(
+ hass, config_entry, async_add_entities, binary=True)
+
+
+class HuePresence(GenericZLLSensor, BinarySensorDevice):
+ """The presence sensor entity for a Hue motion sensor device."""
+
+ device_class = DEVICE_CLASS_MOTION
+
+ async def _async_update_ha_state(self, *args, **kwargs):
+ await self.async_update_ha_state(self, *args, **kwargs)
+
+ @property
+ def is_on(self):
+ """Return true if the binary sensor is on."""
+ return self.sensor.presence
diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py
index 9df5b0a6730..25db031e6bf 100644
--- a/homeassistant/components/hue/bridge.py
+++ b/homeassistant/components/hue/bridge.py
@@ -1,6 +1,7 @@
"""Code to handle a Hue bridge."""
import asyncio
+import aiohue
import async_timeout
import voluptuous as vol
@@ -68,6 +69,10 @@ class HueBridge:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
self.config_entry, 'light'))
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ self.config_entry, 'binary_sensor'))
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ self.config_entry, 'sensor'))
hass.services.async_register(
DOMAIN, SERVICE_HUE_SCENE, self.hue_activate_scene,
@@ -93,8 +98,16 @@ class HueBridge:
# If setup was successful, we set api variable, forwarded entry and
# register service
- return await self.hass.config_entries.async_forward_entry_unload(
- self.config_entry, 'light')
+ results = await asyncio.gather(
+ self.hass.config_entries.async_forward_entry_unload(
+ self.config_entry, 'light'),
+ self.hass.config_entries.async_forward_entry_unload(
+ self.config_entry, 'binary_sensor'),
+ self.hass.config_entries.async_forward_entry_unload(
+ self.config_entry, 'sensor')
+ )
+ # None and True are OK
+ return False not in results
async def hue_activate_scene(self, call, updated=False):
"""Service to call directly into bridge to set scenes."""
@@ -133,8 +146,6 @@ class HueBridge:
async def get_bridge(hass, host, username=None):
"""Create a bridge object and verify authentication."""
- import aiohue
-
bridge = aiohue.Bridge(
host, username=username,
websession=aiohttp_client.async_get_clientsession(hass)
diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py
index 24ad65e1feb..89dc0b9aa67 100644
--- a/homeassistant/components/hue/config_flow.py
+++ b/homeassistant/components/hue/config_flow.py
@@ -3,6 +3,7 @@ import asyncio
import json
import os
+from aiohue.discovery import discover_nupnp
import async_timeout
import voluptuous as vol
@@ -57,8 +58,6 @@ class HueFlowHandler(config_entries.ConfigFlow):
async def async_step_init(self, user_input=None):
"""Handle a flow start."""
- from aiohue.discovery import discover_nupnp
-
if user_input is not None:
self.host = user_input['host']
return await self.async_step_link()
diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py
index 0725c86bd95..a79b0e3ee23 100644
--- a/homeassistant/components/hue/light.py
+++ b/homeassistant/components/hue/light.py
@@ -5,6 +5,7 @@ import logging
from time import monotonic
import random
+import aiohue
import async_timeout
from homeassistant.components import hue
@@ -16,7 +17,6 @@ from homeassistant.components.light import (
Light)
from homeassistant.util import color
-DEPENDENCIES = ['hue']
SCAN_INTERVAL = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@@ -153,8 +153,6 @@ async def async_update_items(hass, bridge, async_add_entities,
request_bridge_update, is_group, current,
progress_waiting):
"""Update either groups or lights from the bridge."""
- import aiohue
-
if is_group:
api_type = 'group'
api = bridge.api.groups
diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json
new file mode 100644
index 00000000000..54a3a11a189
--- /dev/null
+++ b/homeassistant/components/hue/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "hue",
+ "name": "Philips Hue",
+ "documentation": "https://www.home-assistant.io/components/hue",
+ "requirements": [
+ "aiohue==1.9.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@balloob"
+ ]
+}
diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py
new file mode 100644
index 00000000000..30a439f92e9
--- /dev/null
+++ b/homeassistant/components/hue/sensor.py
@@ -0,0 +1,62 @@
+"""Hue sensor entities."""
+from homeassistant.const import (
+ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS)
+from homeassistant.helpers.entity import Entity
+from homeassistant.components.hue.sensor_base import (
+ GenericZLLSensor, async_setup_entry as shared_async_setup_entry)
+
+
+LIGHT_LEVEL_NAME_FORMAT = "{} light level"
+TEMPERATURE_NAME_FORMAT = "{} temperature"
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Defer sensor setup to the shared sensor module."""
+ await shared_async_setup_entry(
+ hass, config_entry, async_add_entities, binary=False)
+
+
+class GenericHueGaugeSensorEntity(GenericZLLSensor, Entity):
+ """Parent class for all 'gauge' Hue device sensors."""
+
+ async def _async_update_ha_state(self, *args, **kwargs):
+ await self.async_update_ha_state(self, *args, **kwargs)
+
+
+class HueLightLevel(GenericHueGaugeSensorEntity):
+ """The light level sensor entity for a Hue motion sensor device."""
+
+ device_class = DEVICE_CLASS_ILLUMINANCE
+ unit_of_measurement = "lx"
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ # https://developers.meethue.com/develop/hue-api/supported-devices/#clip_zll_lightlevel
+ # Light level in 10000 log10 (lux) +1 measured by sensor. Logarithm
+ # scale used because the human eye adjusts to light levels and small
+ # changes at low lux levels are more noticeable than at high lux
+ # levels.
+ return 10 ** ((self.sensor.lightlevel - 1) / 10000)
+
+ @property
+ def device_state_attributes(self):
+ """Return the device state attributes."""
+ attributes = super().device_state_attributes
+ attributes.update({
+ "threshold_dark": self.sensor.tholddark,
+ "threshold_offset": self.sensor.tholdoffset,
+ })
+ return attributes
+
+
+class HueTemperature(GenericHueGaugeSensorEntity):
+ """The temperature sensor entity for a Hue motion sensor device."""
+
+ device_class = DEVICE_CLASS_TEMPERATURE
+ unit_of_measurement = TEMP_CELSIUS
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ return self.sensor.temperature / 100
diff --git a/homeassistant/components/hue/sensor_base.py b/homeassistant/components/hue/sensor_base.py
new file mode 100644
index 00000000000..1d6fa2d34b4
--- /dev/null
+++ b/homeassistant/components/hue/sensor_base.py
@@ -0,0 +1,283 @@
+"""Support for the Philips Hue sensors as a platform."""
+import asyncio
+from datetime import timedelta
+import logging
+from time import monotonic
+
+import async_timeout
+
+from homeassistant.components import hue
+from homeassistant.exceptions import NoEntitySpecifiedError
+from homeassistant.helpers.event import async_track_point_in_utc_time
+from homeassistant.util.dt import utcnow
+
+
+CURRENT_SENSORS = 'current_sensors'
+SENSOR_MANAGER = 'sensor_manager'
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def _device_id(aiohue_sensor):
+ # Work out the shared device ID, as described below
+ device_id = aiohue_sensor.uniqueid
+ if device_id and len(device_id) > 23:
+ device_id = device_id[:23]
+ return device_id
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities,
+ binary=False):
+ """Set up the Hue sensors from a config entry."""
+ bridge = hass.data[hue.DOMAIN][config_entry.data['host']]
+ hass.data[hue.DOMAIN].setdefault(CURRENT_SENSORS, {})
+
+ manager = hass.data[hue.DOMAIN].get(SENSOR_MANAGER)
+ if manager is None:
+ manager = SensorManager(hass, bridge)
+ hass.data[hue.DOMAIN][SENSOR_MANAGER] = manager
+
+ manager.register_component(binary, async_add_entities)
+ await manager.start()
+
+
+class SensorManager:
+ """Class that handles registering and updating Hue sensor entities.
+
+ Intended to be a singleton.
+ """
+
+ SCAN_INTERVAL = timedelta(seconds=5)
+ sensor_config_map = {}
+
+ def __init__(self, hass, bridge):
+ """Initialize the sensor manager."""
+ import aiohue
+ from .binary_sensor import HuePresence, PRESENCE_NAME_FORMAT
+ from .sensor import (
+ HueLightLevel, HueTemperature, LIGHT_LEVEL_NAME_FORMAT,
+ TEMPERATURE_NAME_FORMAT)
+
+ self.hass = hass
+ self.bridge = bridge
+ self._component_add_entities = {}
+ self._started = False
+
+ self.sensor_config_map.update({
+ aiohue.sensors.TYPE_ZLL_LIGHTLEVEL: {
+ "binary": False,
+ "name_format": LIGHT_LEVEL_NAME_FORMAT,
+ "class": HueLightLevel,
+ },
+ aiohue.sensors.TYPE_ZLL_TEMPERATURE: {
+ "binary": False,
+ "name_format": TEMPERATURE_NAME_FORMAT,
+ "class": HueTemperature,
+ },
+ aiohue.sensors.TYPE_ZLL_PRESENCE: {
+ "binary": True,
+ "name_format": PRESENCE_NAME_FORMAT,
+ "class": HuePresence,
+ },
+ })
+
+ def register_component(self, binary, async_add_entities):
+ """Register async_add_entities methods for components."""
+ self._component_add_entities[binary] = async_add_entities
+
+ async def start(self):
+ """Start updating sensors from the bridge on a schedule."""
+ # but only if it's not already started, and when we've got both
+ # async_add_entities methods
+ if self._started or len(self._component_add_entities) < 2:
+ return
+
+ self._started = True
+ _LOGGER.info('Starting sensor polling loop with %s second interval',
+ self.SCAN_INTERVAL.total_seconds())
+
+ async def async_update_bridge(now):
+ """Will update sensors from the bridge."""
+ await self.async_update_items()
+
+ async_track_point_in_utc_time(
+ self.hass, async_update_bridge, utcnow() + self.SCAN_INTERVAL)
+
+ await async_update_bridge(None)
+
+ async def async_update_items(self):
+ """Update sensors from the bridge."""
+ import aiohue
+
+ api = self.bridge.api.sensors
+
+ try:
+ start = monotonic()
+ with async_timeout.timeout(4):
+ await api.update()
+ except (asyncio.TimeoutError, aiohue.AiohueException) as err:
+ _LOGGER.debug('Failed to fetch sensor: %s', err)
+
+ if not self.bridge.available:
+ return
+
+ _LOGGER.error('Unable to reach bridge %s (%s)', self.bridge.host,
+ err)
+ self.bridge.available = False
+
+ return
+
+ finally:
+ _LOGGER.debug('Finished sensor request in %.3f seconds',
+ monotonic() - start)
+
+ if not self.bridge.available:
+ _LOGGER.info('Reconnected to bridge %s', self.bridge.host)
+ self.bridge.available = True
+
+ new_sensors = []
+ new_binary_sensors = []
+ primary_sensor_devices = {}
+ current = self.hass.data[hue.DOMAIN][CURRENT_SENSORS]
+
+ # Physical Hue motion sensors present as three sensors in the API: a
+ # presence sensor, a temperature sensor, and a light level sensor. Of
+ # these, only the presence sensor is assigned the user-friendly name
+ # that the user has given to the device. Each of these sensors is
+ # linked by a common device_id, which is the first twenty-three
+ # characters of the unique id (then followed by a hyphen and an ID
+ # specific to the individual sensor).
+ #
+ # To set up neat values, and assign the sensor entities to the same
+ # device, we first, iterate over all the sensors and find the Hue
+ # presence sensors, then iterate over all the remaining sensors -
+ # finding the remaining ones that may or may not be related to the
+ # presence sensors.
+ for item_id in api:
+ if api[item_id].type != aiohue.sensors.TYPE_ZLL_PRESENCE:
+ continue
+
+ primary_sensor_devices[_device_id(api[item_id])] = api[item_id]
+
+ # Iterate again now we have all the presence sensors, and add the
+ # related sensors with nice names where appropriate.
+ for item_id in api:
+ existing = current.get(api[item_id].uniqueid)
+ if existing is not None:
+ self.hass.async_create_task(
+ existing.async_maybe_update_ha_state())
+ continue
+
+ primary_sensor = None
+ sensor_config = self.sensor_config_map.get(api[item_id].type)
+ if sensor_config is None:
+ continue
+
+ base_name = api[item_id].name
+ primary_sensor = primary_sensor_devices.get(
+ _device_id(api[item_id]))
+ if primary_sensor is not None:
+ base_name = primary_sensor.name
+ name = sensor_config["name_format"].format(base_name)
+
+ current[api[item_id].uniqueid] = sensor_config["class"](
+ api[item_id], name, self.bridge, primary_sensor=primary_sensor)
+ if sensor_config['binary']:
+ new_binary_sensors.append(current[api[item_id].uniqueid])
+ else:
+ new_sensors.append(current[api[item_id].uniqueid])
+
+ async_add_sensor_entities = self._component_add_entities.get(False)
+ async_add_binary_entities = self._component_add_entities.get(True)
+ if new_sensors and async_add_sensor_entities:
+ async_add_sensor_entities(new_sensors)
+ if new_binary_sensors and async_add_binary_entities:
+ async_add_binary_entities(new_binary_sensors)
+
+
+class GenericHueSensor:
+ """Representation of a Hue sensor."""
+
+ should_poll = False
+
+ def __init__(self, sensor, name, bridge, primary_sensor=None):
+ """Initialize the sensor."""
+ self.sensor = sensor
+ self._name = name
+ self._primary_sensor = primary_sensor
+ self.bridge = bridge
+
+ async def _async_update_ha_state(self, *args, **kwargs):
+ raise NotImplementedError
+
+ @property
+ def primary_sensor(self):
+ """Return the primary sensor entity of the physical device."""
+ return self._primary_sensor or self.sensor
+
+ @property
+ def device_id(self):
+ """Return the ID of the physical device this sensor is part of."""
+ return self.unique_id[:23]
+
+ @property
+ def unique_id(self):
+ """Return the ID of this Hue sensor."""
+ return self.sensor.uniqueid
+
+ @property
+ def name(self):
+ """Return a friendly name for the sensor."""
+ return self._name
+
+ @property
+ def available(self):
+ """Return if sensor is available."""
+ return self.bridge.available and (self.bridge.allow_unreachable or
+ self.sensor.config['reachable'])
+
+ @property
+ def swupdatestate(self):
+ """Return detail of available software updates for this device."""
+ return self.primary_sensor.raw.get('swupdate', {}).get('state')
+
+ async def async_maybe_update_ha_state(self):
+ """Try to update Home Assistant with current state of entity.
+
+ But if it's not been added to hass yet, then don't throw an error.
+ """
+ try:
+ await self._async_update_ha_state()
+ except (RuntimeError, NoEntitySpecifiedError):
+ _LOGGER.debug(
+ "Hue sensor update requested before it has been added.")
+
+ @property
+ def device_info(self):
+ """Return the device info.
+
+ Links individual entities together in the hass device registry.
+ """
+ return {
+ 'identifiers': {
+ (hue.DOMAIN, self.device_id)
+ },
+ 'name': self.primary_sensor.name,
+ 'manufacturer': self.primary_sensor.manufacturername,
+ 'model': (
+ self.primary_sensor.productname or
+ self.primary_sensor.modelid),
+ 'sw_version': self.primary_sensor.swversion,
+ 'via_hub': (hue.DOMAIN, self.bridge.api.config.bridgeid),
+ }
+
+
+class GenericZLLSensor(GenericHueSensor):
+ """Representation of a Hue-brand, physical sensor."""
+
+ @property
+ def device_state_attributes(self):
+ """Return the device state attributes."""
+ return {
+ "battery_level": self.sensor.battery
+ }
diff --git a/homeassistant/components/hue/services.yaml b/homeassistant/components/hue/services.yaml
new file mode 100644
index 00000000000..68eaf6ac377
--- /dev/null
+++ b/homeassistant/components/hue/services.yaml
@@ -0,0 +1,11 @@
+# Describes the format for available hue services
+
+hue_activate_scene:
+ description: Activate a hue scene stored in the hue hub.
+ fields:
+ group_name:
+ description: Name of hue group/room from the hue app.
+ example: "Living Room"
+ scene_name:
+ description: Name of hue scene from the hue app.
+ example: "Energize"
diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json
new file mode 100644
index 00000000000..c4e1bcc28e8
--- /dev/null
+++ b/homeassistant/components/hunterdouglas_powerview/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hunterdouglas_powerview",
+ "name": "Hunterdouglas powerview",
+ "documentation": "https://www.home-assistant.io/components/hunterdouglas_powerview",
+ "requirements": [
+ "aiopvapi==1.6.14"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py
index 7f0709aa6c1..571e15ab94f 100644
--- a/homeassistant/components/hunterdouglas_powerview/scene.py
+++ b/homeassistant/components/hunterdouglas_powerview/scene.py
@@ -10,8 +10,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import async_generate_entity_id
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['aiopvapi==1.6.14']
-
ENTITY_ID_FORMAT = DOMAIN + '.{}'
HUB_ADDRESS = 'address'
diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py
index 9c7baf6db2e..6ac0ee0322d 100644
--- a/homeassistant/components/hydrawise/__init__.py
+++ b/homeassistant/components/hydrawise/__init__.py
@@ -14,8 +14,6 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['hydrawiser==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
ALLOWED_WATERING_TIME = [5, 10, 15, 30, 45, 60]
diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py
index 85a51d3649e..980e495c7f9 100644
--- a/homeassistant/components/hydrawise/binary_sensor.py
+++ b/homeassistant/components/hydrawise/binary_sensor.py
@@ -12,8 +12,6 @@ from . import (
BINARY_SENSORS, DATA_HYDRAWISE, DEVICE_MAP, DEVICE_MAP_INDEX,
HydrawiseEntity)
-DEPENDENCIES = ['hydrawise']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/hydrawise/manifest.json b/homeassistant/components/hydrawise/manifest.json
new file mode 100644
index 00000000000..6d332a28bcc
--- /dev/null
+++ b/homeassistant/components/hydrawise/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hydrawise",
+ "name": "Hydrawise",
+ "documentation": "https://www.home-assistant.io/components/hydrawise",
+ "requirements": [
+ "hydrawiser==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py
index fc15a54ed60..908529c783d 100644
--- a/homeassistant/components/hydrawise/sensor.py
+++ b/homeassistant/components/hydrawise/sensor.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from . import (
DATA_HYDRAWISE, DEVICE_MAP, DEVICE_MAP_INDEX, SENSORS, HydrawiseEntity)
-DEPENDENCIES = ['hydrawise']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py
index dcbd5274a62..ccfa9333e00 100644
--- a/homeassistant/components/hydrawise/switch.py
+++ b/homeassistant/components/hydrawise/switch.py
@@ -12,8 +12,6 @@ from . import (
DEFAULT_WATERING_TIME, DEVICE_MAP, DEVICE_MAP_INDEX, SWITCHES,
HydrawiseEntity)
-DEPENDENCIES = ['hydrawise']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/hydroquebec/manifest.json b/homeassistant/components/hydroquebec/manifest.json
new file mode 100644
index 00000000000..efea5ce0f2e
--- /dev/null
+++ b/homeassistant/components/hydroquebec/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "hydroquebec",
+ "name": "Hydroquebec",
+ "documentation": "https://www.home-assistant.io/components/hydroquebec",
+ "requirements": [
+ "pyhydroquebec==2.2.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/hydroquebec/sensor.py b/homeassistant/components/hydroquebec/sensor.py
index 5f0fd9e01ad..0ec48f3058d 100644
--- a/homeassistant/components/hydroquebec/sensor.py
+++ b/homeassistant/components/hydroquebec/sensor.py
@@ -20,8 +20,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyhydroquebec==2.2.2']
-
_LOGGER = logging.getLogger(__name__)
KILOWATT_HOUR = ENERGY_KILO_WATT_HOUR
diff --git a/homeassistant/components/hyperion/manifest.json b/homeassistant/components/hyperion/manifest.json
new file mode 100644
index 00000000000..980c227944a
--- /dev/null
+++ b/homeassistant/components/hyperion/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "hyperion",
+ "name": "Hyperion",
+ "documentation": "https://www.home-assistant.io/components/hyperion",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ialarm/alarm_control_panel.py b/homeassistant/components/ialarm/alarm_control_panel.py
index df975ef00ac..27ff4fc6829 100644
--- a/homeassistant/components/ialarm/alarm_control_panel.py
+++ b/homeassistant/components/ialarm/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Interfaces with iAlarm control panels.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.ialarm/
-"""
+"""Interfaces with iAlarm control panels."""
import logging
import re
@@ -17,8 +12,6 @@ from homeassistant.const import (
STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyialarm==0.3']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'iAlarm'
diff --git a/homeassistant/components/ialarm/manifest.json b/homeassistant/components/ialarm/manifest.json
new file mode 100644
index 00000000000..df492d136fd
--- /dev/null
+++ b/homeassistant/components/ialarm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ialarm",
+ "name": "Ialarm",
+ "documentation": "https://www.home-assistant.io/components/ialarm",
+ "requirements": [
+ "pyialarm==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py
index 9ac77cf7f01..908fe5ecf90 100644
--- a/homeassistant/components/icloud/device_tracker.py
+++ b/homeassistant/components/icloud/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Platform that supports scanning iCloud.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.icloud/
-"""
+"""Platform that supports scanning iCloud."""
import logging
import random
import os
@@ -22,8 +17,6 @@ from homeassistant.util.location import distance
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyicloud==0.9.1']
-
CONF_ACCOUNTNAME = 'account_name'
CONF_MAX_INTERVAL = 'max_interval'
CONF_GPS_ACCURACY_THRESHOLD = 'gps_accuracy_threshold'
diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json
new file mode 100644
index 00000000000..5f2075a0fd6
--- /dev/null
+++ b/homeassistant/components/icloud/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "icloud",
+ "name": "Icloud",
+ "documentation": "https://www.home-assistant.io/components/icloud",
+ "requirements": [
+ "pyicloud==0.9.1"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/icloud/services.yaml b/homeassistant/components/icloud/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/idteck_prox/__init__.py b/homeassistant/components/idteck_prox/__init__.py
index 3de7aa7cc8c..bfb227e0fc1 100644
--- a/homeassistant/components/idteck_prox/__init__.py
+++ b/homeassistant/components/idteck_prox/__init__.py
@@ -7,8 +7,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['rfk101py==0.0.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = "idteck_prox"
diff --git a/homeassistant/components/idteck_prox/manifest.json b/homeassistant/components/idteck_prox/manifest.json
new file mode 100644
index 00000000000..8df144a0f81
--- /dev/null
+++ b/homeassistant/components/idteck_prox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "idteck_prox",
+ "name": "Idteck prox",
+ "documentation": "https://www.home-assistant.io/components/idteck_prox",
+ "requirements": [
+ "rfk101py==0.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ifttt/.translations/nn.json b/homeassistant/components/ifttt/.translations/nn.json
new file mode 100644
index 00000000000..e3bef7270e5
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/nn.json
@@ -0,0 +1,10 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "description": "Er du sikker p\u00e5 at du \u00f8nskjer \u00e5 setta opp IFTTT?"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/ru.json b/homeassistant/components/ifttt/.translations/ru.json
index 4184d2dfadc..ae5fdbab3f6 100644
--- a/homeassistant/components/ifttt/.translations/ru.json
+++ b/homeassistant/components/ifttt/.translations/ru.json
@@ -10,7 +10,7 @@
"step": {
"user": {
"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 IFTTT?",
- "title": "IFTTT Webhook"
+ "title": "IFTTT"
}
},
"title": "IFTTT"
diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py
index 4ab361d41eb..6b5934702aa 100644
--- a/homeassistant/components/ifttt/__init__.py
+++ b/homeassistant/components/ifttt/__init__.py
@@ -9,9 +9,6 @@ from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyfttt==0.3']
-DEPENDENCIES = ['webhook']
-
_LOGGER = logging.getLogger(__name__)
EVENT_RECEIVED = 'ifttt_webhook_received'
@@ -108,6 +105,11 @@ async def async_unload_entry(hass, entry):
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'IFTTT Webhook',
diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py
index 3f806173196..a0492a210e0 100644
--- a/homeassistant/components/ifttt/alarm_control_panel.py
+++ b/homeassistant/components/ifttt/alarm_control_panel.py
@@ -15,8 +15,6 @@ import homeassistant.helpers.config_validation as cv
from . import ATTR_EVENT, DOMAIN as IFTTT_DOMAIN, SERVICE_TRIGGER
-DEPENDENCIES = ['ifttt']
-
_LOGGER = logging.getLogger(__name__)
ALLOWED_STATES = [
diff --git a/homeassistant/components/ifttt/manifest.json b/homeassistant/components/ifttt/manifest.json
new file mode 100644
index 00000000000..007e0870023
--- /dev/null
+++ b/homeassistant/components/ifttt/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ifttt",
+ "name": "Ifttt",
+ "documentation": "https://www.home-assistant.io/components/ifttt",
+ "requirements": [
+ "pyfttt==0.3"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ifttt/services.yaml b/homeassistant/components/ifttt/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/iglo/light.py b/homeassistant/components/iglo/light.py
index 9dca5f8e5f5..1a6b5839029 100644
--- a/homeassistant/components/iglo/light.py
+++ b/homeassistant/components/iglo/light.py
@@ -1,9 +1,4 @@
-"""
-Support for lights under the iGlo brand.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.iglo/
-"""
+"""Support for lights under the iGlo brand."""
import logging
import math
@@ -17,8 +12,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
-REQUIREMENTS = ['iglo==1.2.7']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'iGlo Light'
diff --git a/homeassistant/components/iglo/manifest.json b/homeassistant/components/iglo/manifest.json
new file mode 100644
index 00000000000..4d84c27cd93
--- /dev/null
+++ b/homeassistant/components/iglo/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "iglo",
+ "name": "Iglo",
+ "documentation": "https://www.home-assistant.io/components/iglo",
+ "requirements": [
+ "iglo==1.2.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ign_sismologia/__init__.py b/homeassistant/components/ign_sismologia/__init__.py
new file mode 100644
index 00000000000..0f9f82f8632
--- /dev/null
+++ b/homeassistant/components/ign_sismologia/__init__.py
@@ -0,0 +1 @@
+"""The ign_sismologia component."""
diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py
new file mode 100644
index 00000000000..e2d9d6510bd
--- /dev/null
+++ b/homeassistant/components/ign_sismologia/geo_location.py
@@ -0,0 +1,232 @@
+"""Support for IGN Sismologia (Earthquakes) Feeds."""
+from datetime import timedelta
+import logging
+from typing import Optional
+
+import voluptuous as vol
+
+from homeassistant.components.geo_location import (
+ PLATFORM_SCHEMA, GeolocationEvent)
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE,
+ CONF_RADIUS, CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START)
+from homeassistant.core import callback
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import (
+ async_dispatcher_connect, dispatcher_send)
+from homeassistant.helpers.event import track_time_interval
+
+REQUIREMENTS = ['georss_ign_sismologia_client==0.2']
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_EXTERNAL_ID = 'external_id'
+ATTR_IMAGE_URL = 'image_url'
+ATTR_MAGNITUDE = 'magnitude'
+ATTR_PUBLICATION_DATE = 'publication_date'
+ATTR_REGION = 'region'
+ATTR_TITLE = 'title'
+
+CONF_MINIMUM_MAGNITUDE = 'minimum_magnitude'
+
+DEFAULT_MINIMUM_MAGNITUDE = 0.0
+DEFAULT_RADIUS_IN_KM = 50.0
+DEFAULT_UNIT_OF_MEASUREMENT = 'km'
+
+SCAN_INTERVAL = timedelta(minutes=5)
+
+SIGNAL_DELETE_ENTITY = 'ign_sismologia_delete_{}'
+SIGNAL_UPDATE_ENTITY = 'ign_sismologia_update_{}'
+
+SOURCE = 'ign_sismologia'
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_LATITUDE): cv.latitude,
+ vol.Optional(CONF_LONGITUDE): cv.longitude,
+ vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float),
+ vol.Optional(CONF_MINIMUM_MAGNITUDE, default=DEFAULT_MINIMUM_MAGNITUDE):
+ vol.All(vol.Coerce(float), vol.Range(min=0))
+})
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the IGN Sismologia Feed platform."""
+ scan_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
+ coordinates = (config.get(CONF_LATITUDE, hass.config.latitude),
+ config.get(CONF_LONGITUDE, hass.config.longitude))
+ radius_in_km = config[CONF_RADIUS]
+ minimum_magnitude = config[CONF_MINIMUM_MAGNITUDE]
+ # Initialize the entity manager.
+ feed = IgnSismologiaFeedEntityManager(
+ hass, add_entities, scan_interval, coordinates, radius_in_km,
+ minimum_magnitude)
+
+ def start_feed_manager(event):
+ """Start feed manager."""
+ feed.startup()
+
+ hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_feed_manager)
+
+
+class IgnSismologiaFeedEntityManager:
+ """Feed Entity Manager for IGN Sismologia GeoRSS feed."""
+
+ def __init__(self, hass, add_entities, scan_interval, coordinates,
+ radius_in_km, minimum_magnitude):
+ """Initialize the Feed Entity Manager."""
+ from georss_ign_sismologia_client import IgnSismologiaFeedManager
+
+ self._hass = hass
+ self._feed_manager = IgnSismologiaFeedManager(
+ self._generate_entity, self._update_entity, self._remove_entity,
+ coordinates, filter_radius=radius_in_km,
+ filter_minimum_magnitude=minimum_magnitude)
+ self._add_entities = add_entities
+ self._scan_interval = scan_interval
+
+ def startup(self):
+ """Start up this manager."""
+ self._feed_manager.update()
+ self._init_regular_updates()
+
+ def _init_regular_updates(self):
+ """Schedule regular updates at the specified interval."""
+ track_time_interval(
+ self._hass, lambda now: self._feed_manager.update(),
+ self._scan_interval)
+
+ def get_entry(self, external_id):
+ """Get feed entry by external id."""
+ return self._feed_manager.feed_entries.get(external_id)
+
+ def _generate_entity(self, external_id):
+ """Generate new entity."""
+ new_entity = IgnSismologiaLocationEvent(self, external_id)
+ # Add new entities to HA.
+ self._add_entities([new_entity], True)
+
+ def _update_entity(self, external_id):
+ """Update entity."""
+ dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY.format(external_id))
+
+ def _remove_entity(self, external_id):
+ """Remove entity."""
+ dispatcher_send(self._hass, SIGNAL_DELETE_ENTITY.format(external_id))
+
+
+class IgnSismologiaLocationEvent(GeolocationEvent):
+ """This represents an external event with IGN Sismologia feed data."""
+
+ def __init__(self, feed_manager, external_id):
+ """Initialize entity with data from feed entry."""
+ self._feed_manager = feed_manager
+ self._external_id = external_id
+ self._title = None
+ self._distance = None
+ self._latitude = None
+ self._longitude = None
+ self._attribution = None
+ self._region = None
+ self._magnitude = None
+ self._publication_date = None
+ self._image_url = None
+ self._remove_signal_delete = None
+ self._remove_signal_update = None
+
+ async def async_added_to_hass(self):
+ """Call when entity is added to hass."""
+ self._remove_signal_delete = async_dispatcher_connect(
+ self.hass, SIGNAL_DELETE_ENTITY.format(self._external_id),
+ self._delete_callback)
+ self._remove_signal_update = async_dispatcher_connect(
+ self.hass, SIGNAL_UPDATE_ENTITY.format(self._external_id),
+ self._update_callback)
+
+ @callback
+ def _delete_callback(self):
+ """Remove this entity."""
+ self._remove_signal_delete()
+ self._remove_signal_update()
+ self.hass.async_create_task(self.async_remove())
+
+ @callback
+ def _update_callback(self):
+ """Call update method."""
+ self.async_schedule_update_ha_state(True)
+
+ @property
+ def should_poll(self):
+ """No polling needed for IGN Sismologia feed location events."""
+ return False
+
+ async def async_update(self):
+ """Update this entity from the data held in the feed manager."""
+ _LOGGER.debug("Updating %s", self._external_id)
+ feed_entry = self._feed_manager.get_entry(self._external_id)
+ if feed_entry:
+ self._update_from_feed(feed_entry)
+
+ def _update_from_feed(self, feed_entry):
+ """Update the internal state from the provided feed entry."""
+ self._title = feed_entry.title
+ self._distance = feed_entry.distance_to_home
+ self._latitude = feed_entry.coordinates[0]
+ self._longitude = feed_entry.coordinates[1]
+ self._attribution = feed_entry.attribution
+ self._region = feed_entry.region
+ self._magnitude = feed_entry.magnitude
+ self._publication_date = feed_entry.published
+ self._image_url = feed_entry.image_url
+
+ @property
+ def source(self) -> str:
+ """Return source value of this external event."""
+ return SOURCE
+
+ @property
+ def name(self) -> Optional[str]:
+ """Return the name of the entity."""
+ if self._magnitude and self._region:
+ return "M {:.1f} - {}".format(self._magnitude, self._region)
+ if self._magnitude:
+ return "M {:.1f}".format(self._magnitude)
+ if self._region:
+ return self._region
+ return self._title
+
+ @property
+ def distance(self) -> Optional[float]:
+ """Return distance value of this external event."""
+ return self._distance
+
+ @property
+ def latitude(self) -> Optional[float]:
+ """Return latitude value of this external event."""
+ return self._latitude
+
+ @property
+ def longitude(self) -> Optional[float]:
+ """Return longitude value of this external event."""
+ return self._longitude
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit of measurement."""
+ return DEFAULT_UNIT_OF_MEASUREMENT
+
+ @property
+ def device_state_attributes(self):
+ """Return the device state attributes."""
+ attributes = {}
+ for key, value in (
+ (ATTR_EXTERNAL_ID, self._external_id),
+ (ATTR_TITLE, self._title),
+ (ATTR_REGION, self._region),
+ (ATTR_MAGNITUDE, self._magnitude),
+ (ATTR_ATTRIBUTION, self._attribution),
+ (ATTR_PUBLICATION_DATE, self._publication_date),
+ (ATTR_IMAGE_URL, self._image_url)
+ ):
+ if value or isinstance(value, bool):
+ attributes[key] = value
+ return attributes
diff --git a/homeassistant/components/ign_sismologia/manifest.json b/homeassistant/components/ign_sismologia/manifest.json
new file mode 100644
index 00000000000..d2ab3ad449c
--- /dev/null
+++ b/homeassistant/components/ign_sismologia/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ign_sismologia",
+ "name": "IGN Sismologia",
+ "documentation": "https://www.home-assistant.io/components/ign_sismologia",
+ "requirements": [
+ "georss_ign_sismologia_client==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@exxamalte"
+ ]
+}
\ No newline at end of file
diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py
index 102acd82551..7d8acfbdf2e 100644
--- a/homeassistant/components/ihc/__init__.py
+++ b/homeassistant/components/ihc/__init__.py
@@ -21,8 +21,6 @@ from .const import (
SERVICE_SET_RUNTIME_VALUE_FLOAT, SERVICE_SET_RUNTIME_VALUE_INT)
from .util import async_pulse
-REQUIREMENTS = ['ihcsdk==2.3.0', 'defusedxml==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
AUTO_SETUP_YAML = 'ihc_auto_setup.yaml'
diff --git a/homeassistant/components/ihc/binary_sensor.py b/homeassistant/components/ihc/binary_sensor.py
index 69e3e1685af..a9a2b66cdde 100644
--- a/homeassistant/components/ihc/binary_sensor.py
+++ b/homeassistant/components/ihc/binary_sensor.py
@@ -6,8 +6,6 @@ from . import IHC_CONTROLLER, IHC_DATA, IHC_INFO
from .const import CONF_INVERTING
from .ihcdevice import IHCDevice
-DEPENDENCIES = ['ihc']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the IHC binary sensor platform."""
diff --git a/homeassistant/components/ihc/light.py b/homeassistant/components/ihc/light.py
index ad6d0fb6511..72c0dc8f0ba 100644
--- a/homeassistant/components/ihc/light.py
+++ b/homeassistant/components/ihc/light.py
@@ -9,8 +9,6 @@ from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID
from .ihcdevice import IHCDevice
from .util import async_pulse, async_set_bool, async_set_int
-DEPENDENCIES = ['ihc']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/ihc/manifest.json b/homeassistant/components/ihc/manifest.json
new file mode 100644
index 00000000000..bbcd4ab9389
--- /dev/null
+++ b/homeassistant/components/ihc/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "ihc",
+ "name": "Ihc",
+ "documentation": "https://www.home-assistant.io/components/ihc",
+ "requirements": [
+ "defusedxml==0.5.0",
+ "ihcsdk==2.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ihc/sensor.py b/homeassistant/components/ihc/sensor.py
index fd1f2cee53a..4c63cf41e96 100644
--- a/homeassistant/components/ihc/sensor.py
+++ b/homeassistant/components/ihc/sensor.py
@@ -5,8 +5,6 @@ from homeassistant.helpers.entity import Entity
from . import IHC_CONTROLLER, IHC_DATA, IHC_INFO
from .ihcdevice import IHCDevice
-DEPENDENCIES = ['ihc']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the IHC sensor platform."""
diff --git a/homeassistant/components/ihc/switch.py b/homeassistant/components/ihc/switch.py
index e2189492b8f..6d3a72a3b66 100644
--- a/homeassistant/components/ihc/switch.py
+++ b/homeassistant/components/ihc/switch.py
@@ -6,8 +6,6 @@ from .const import CONF_OFF_ID, CONF_ON_ID
from .ihcdevice import IHCDevice
from .util import async_pulse, async_set_bool
-DEPENDENCIES = ['ihc']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the IHC switch platform."""
diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py
index aa3b2db7369..ce49ebf932e 100644
--- a/homeassistant/components/image_processing/__init__.py
+++ b/homeassistant/components/image_processing/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provides functionality to interact with image processing services.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/image_processing/
-"""
+"""Provides functionality to interact with image processing services."""
import asyncio
from datetime import timedelta
import logging
@@ -22,8 +17,6 @@ from homeassistant.util.async_ import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'image_processing'
-DEPENDENCIES = ['camera']
-
SCAN_INTERVAL = timedelta(seconds=10)
DEVICE_CLASSES = [
diff --git a/homeassistant/components/image_processing/manifest.json b/homeassistant/components/image_processing/manifest.json
new file mode 100644
index 00000000000..e675d18a00b
--- /dev/null
+++ b/homeassistant/components/image_processing/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "image_processing",
+ "name": "Image processing",
+ "documentation": "https://www.home-assistant.io/components/image_processing",
+ "requirements": [],
+ "dependencies": [
+ "camera"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/imap/manifest.json b/homeassistant/components/imap/manifest.json
new file mode 100644
index 00000000000..9e0f387a7a6
--- /dev/null
+++ b/homeassistant/components/imap/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "imap",
+ "name": "Imap",
+ "documentation": "https://www.home-assistant.io/components/imap",
+ "requirements": [
+ "aioimaplib==0.7.15"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/imap/sensor.py b/homeassistant/components/imap/sensor.py
index 571d05e78e9..cbc470beec8 100644
--- a/homeassistant/components/imap/sensor.py
+++ b/homeassistant/components/imap/sensor.py
@@ -1,9 +1,4 @@
-"""
-IMAP sensor support.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.imap/
-"""
+"""IMAP sensor support."""
import asyncio
import logging
@@ -20,8 +15,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['aioimaplib==0.7.15']
-
CONF_SERVER = 'server'
CONF_FOLDER = 'folder'
CONF_SEARCH = 'search'
diff --git a/homeassistant/components/imap_email_content/manifest.json b/homeassistant/components/imap_email_content/manifest.json
new file mode 100644
index 00000000000..a1e2c616832
--- /dev/null
+++ b/homeassistant/components/imap_email_content/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "imap_email_content",
+ "name": "Imap email content",
+ "documentation": "https://www.home-assistant.io/components/imap_email_content",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py
index 714a6c38781..950007b462b 100644
--- a/homeassistant/components/imap_email_content/sensor.py
+++ b/homeassistant/components/imap_email_content/sensor.py
@@ -1,9 +1,4 @@
-"""
-Email sensor support.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.imap_email_content/
-"""
+"""Email sensor support."""
import logging
import datetime
import email
diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py
index b421960b51f..bf2ba1b8ecc 100644
--- a/homeassistant/components/influxdb/__init__.py
+++ b/homeassistant/components/influxdb/__init__.py
@@ -14,12 +14,10 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL,
EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_STOP, STATE_UNAVAILABLE,
STATE_UNKNOWN)
-from homeassistant.helpers import state as state_helper
+from homeassistant.helpers import state as state_helper, event as event_helper
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_values import EntityValues
-REQUIREMENTS = ['influxdb==5.2.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_DB_NAME = 'database'
@@ -39,6 +37,7 @@ DOMAIN = 'influxdb'
TIMEOUT = 5
RETRY_DELAY = 20
QUEUE_BACKLOG_SECONDS = 30
+RETRY_INTERVAL = 60 # seconds
BATCH_TIMEOUT = 1
BATCH_BUFFER_SIZE = 100
@@ -134,11 +133,16 @@ def setup(hass, config):
influx.write_points([])
except (exceptions.InfluxDBClientError,
requests.exceptions.ConnectionError) as exc:
- _LOGGER.error("Database host is not accessible due to '%s', please "
- "check your entries in the configuration file (host, "
- "port, etc.) and verify that the database exists and is "
- "READ/WRITE", exc)
- return False
+ _LOGGER.warning(
+ "Database host is not accessible due to '%s', please "
+ "check your entries in the configuration file (host, "
+ "port, etc.) and verify that the database exists and is "
+ "READ/WRITE. Retrying again in %s seconds.", exc, RETRY_INTERVAL
+ )
+ event_helper.call_later(
+ hass, RETRY_INTERVAL, lambda _: setup(hass, config)
+ )
+ return True
def event_to_json(event):
"""Add an event to the outgoing Influx list."""
diff --git a/homeassistant/components/influxdb/manifest.json b/homeassistant/components/influxdb/manifest.json
new file mode 100644
index 00000000000..20652ddd046
--- /dev/null
+++ b/homeassistant/components/influxdb/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "influxdb",
+ "name": "Influxdb",
+ "documentation": "https://www.home-assistant.io/components/influxdb",
+ "requirements": [
+ "influxdb==5.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py
index 9dbb4787df7..81a93cfc51d 100644
--- a/homeassistant/components/influxdb/sensor.py
+++ b/homeassistant/components/influxdb/sensor.py
@@ -1,9 +1,4 @@
-"""
-InfluxDB component which allows you to get data from an Influx database.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.influxdb/
-"""
+"""InfluxDB component which allows you to get data from an Influx database."""
from datetime import timedelta
import logging
@@ -23,8 +18,6 @@ from . import CONF_DB_NAME
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['influxdb==5.2.0']
-
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8086
DEFAULT_DATABASE = 'home_assistant'
diff --git a/homeassistant/components/input_boolean/manifest.json b/homeassistant/components/input_boolean/manifest.json
new file mode 100644
index 00000000000..e233b5635fc
--- /dev/null
+++ b/homeassistant/components/input_boolean/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "input_boolean",
+ "name": "Input boolean",
+ "documentation": "https://www.home-assistant.io/components/input_boolean",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/input_datetime/manifest.json b/homeassistant/components/input_datetime/manifest.json
new file mode 100644
index 00000000000..287777e2ccf
--- /dev/null
+++ b/homeassistant/components/input_datetime/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "input_datetime",
+ "name": "Input datetime",
+ "documentation": "https://www.home-assistant.io/components/input_datetime",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/input_datetime/services.yaml b/homeassistant/components/input_datetime/services.yaml
new file mode 100644
index 00000000000..9534ad3f696
--- /dev/null
+++ b/homeassistant/components/input_datetime/services.yaml
@@ -0,0 +1,9 @@
+set_datetime:
+ description: This can be used to dynamically set the date and/or time.
+ fields:
+ entity_id: {description: Entity id of the input datetime to set the new value.,
+ example: input_datetime.test_date_time}
+ date: {description: The target date the entity should be set to.,
+ example: '"date": "2019-04-22"'}
+ time: {description: The target time the entity should be set to.,
+ example: '"time": "05:30:00"'}
diff --git a/homeassistant/components/input_number/manifest.json b/homeassistant/components/input_number/manifest.json
new file mode 100644
index 00000000000..2015b8ea734
--- /dev/null
+++ b/homeassistant/components/input_number/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "input_number",
+ "name": "Input number",
+ "documentation": "https://www.home-assistant.io/components/input_number",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/input_select/manifest.json b/homeassistant/components/input_select/manifest.json
new file mode 100644
index 00000000000..a71fb53a5d1
--- /dev/null
+++ b/homeassistant/components/input_select/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "input_select",
+ "name": "Input select",
+ "documentation": "https://www.home-assistant.io/components/input_select",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/input_text/manifest.json b/homeassistant/components/input_text/manifest.json
new file mode 100644
index 00000000000..6362e679319
--- /dev/null
+++ b/homeassistant/components/input_text/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "input_text",
+ "name": "Input text",
+ "documentation": "https://www.home-assistant.io/components/input_text",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py
index a462ac0f63e..a1eea2fb1df 100644
--- a/homeassistant/components/insteon/__init__.py
+++ b/homeassistant/components/insteon/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['insteonplm==0.15.2']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'insteon'
diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py
index 6f1e5675639..50e7a8fb646 100644
--- a/homeassistant/components/insteon/binary_sensor.py
+++ b/homeassistant/components/insteon/binary_sensor.py
@@ -7,8 +7,6 @@ from . import InsteonEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['insteon']
-
SENSOR_TYPES = {
'openClosedSensor': 'opening',
'ioLincSensor': 'opening',
diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py
index 1bb316152a9..da339bb4b65 100644
--- a/homeassistant/components/insteon/cover.py
+++ b/homeassistant/components/insteon/cover.py
@@ -10,7 +10,6 @@ from . import InsteonEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['insteon']
SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py
index 26a56d6df98..888fcfe959a 100644
--- a/homeassistant/components/insteon/fan.py
+++ b/homeassistant/components/insteon/fan.py
@@ -10,8 +10,6 @@ from . import InsteonEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['insteon']
-
SPEED_TO_HEX = {
SPEED_OFF: 0x00,
SPEED_LOW: 0x3f,
diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py
index 676c053325c..5103bedc6b6 100644
--- a/homeassistant/components/insteon/light.py
+++ b/homeassistant/components/insteon/light.py
@@ -8,8 +8,6 @@ from . import InsteonEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['insteon']
-
MAX_BRIGHTNESS = 255
diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json
new file mode 100644
index 00000000000..7ba27cbe625
--- /dev/null
+++ b/homeassistant/components/insteon/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "insteon",
+ "name": "Insteon",
+ "documentation": "https://www.home-assistant.io/components/insteon",
+ "requirements": [
+ "insteonplm==0.15.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/insteon/sensor.py b/homeassistant/components/insteon/sensor.py
index edea87e1f73..a7c1c0b89ef 100644
--- a/homeassistant/components/insteon/sensor.py
+++ b/homeassistant/components/insteon/sensor.py
@@ -5,8 +5,6 @@ from homeassistant.helpers.entity import Entity
from . import InsteonEntity
-DEPENDENCIES = ['insteon']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py
index 4fdcdb20bb2..6c7b2b02031 100644
--- a/homeassistant/components/insteon/switch.py
+++ b/homeassistant/components/insteon/switch.py
@@ -5,8 +5,6 @@ from homeassistant.components.switch import SwitchDevice
from . import InsteonEntity
-DEPENDENCIES = ['insteon']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/insteon_local/__init__.py b/homeassistant/components/insteon_local/__init__.py
deleted file mode 100644
index f73c46746f0..00000000000
--- a/homeassistant/components/insteon_local/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""Local support for Insteon."""
-import logging
-
-_LOGGER = logging.getLogger(__name__)
-
-
-def setup(hass, config):
- """Set up the insteon_local component.
-
- This component is deprecated as of release 0.77 and should be removed in
- release 0.90.
- """
- _LOGGER.warning('The insteon_local component has been replaced by '
- 'the insteon component')
- _LOGGER.warning('Please see https://home-assistant.io/components/insteon')
-
- hass.components.persistent_notification.create(
- 'insteon_local has been replaced by the insteon component.
'
- 'Please see https://home-assistant.io/components/insteon',
- title='insteon_local Component Deactivated',
- notification_id='insteon_local')
-
- return False
diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon_plm/__init__.py
deleted file mode 100644
index 5ff492b6f6c..00000000000
--- a/homeassistant/components/insteon_plm/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""Support for INSTEON PowerLinc Modem."""
-import logging
-
-_LOGGER = logging.getLogger(__name__)
-
-
-async def async_setup(hass, config):
- """Set up the insteon_plm component.
-
- This component is deprecated as of release 0.77 and should be removed in
- release 0.90.
- """
- _LOGGER.warning('The insteon_plm component has been replaced by '
- 'the insteon component')
- _LOGGER.warning('Please see https://home-assistant.io/components/insteon')
-
- hass.components.persistent_notification.create(
- 'insteon_plm has been replaced by the insteon component.
'
- 'Please see https://home-assistant.io/components/insteon',
- title='insteon_plm Component Deactivated',
- notification_id='insteon_plm')
-
- return False
diff --git a/homeassistant/components/integration/manifest.json b/homeassistant/components/integration/manifest.json
new file mode 100644
index 00000000000..869ad2766f9
--- /dev/null
+++ b/homeassistant/components/integration/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "integration",
+ "name": "Integration",
+ "documentation": "https://www.home-assistant.io/components/integration",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py
index 9250c8dde05..3a72c81fa11 100644
--- a/homeassistant/components/integration/sensor.py
+++ b/homeassistant/components/integration/sensor.py
@@ -1,9 +1,4 @@
-"""
-Numeric integration of data coming from a source sensor over time.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.integration/
-"""
+"""Numeric integration of data coming from a source sensor over time."""
import logging
from decimal import Decimal, DecimalException
diff --git a/homeassistant/components/intent_script/manifest.json b/homeassistant/components/intent_script/manifest.json
new file mode 100644
index 00000000000..891be6b2180
--- /dev/null
+++ b/homeassistant/components/intent_script/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "intent_script",
+ "name": "Intent script",
+ "documentation": "https://www.home-assistant.io/components/intent_script",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/introduction/__init__.py b/homeassistant/components/introduction/__init__.py
deleted file mode 100644
index 8a2d72ebbdd..00000000000
--- a/homeassistant/components/introduction/__init__.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""Component that will help guide the user taking its first steps."""
-import logging
-
-import voluptuous as vol
-
-DOMAIN = 'introduction'
-
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({}),
-}, extra=vol.ALLOW_EXTRA)
-
-
-async def async_setup(hass, config=None):
- """Set up the introduction component."""
- log = logging.getLogger(__name__)
- log.info("""
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Hello, and welcome to Home Assistant!
-
- We'll hope that we can make all your dreams come true.
-
- Here are some resources to get started:
-
- - Configuring Home Assistant:
- https://home-assistant.io/getting-started/configuration/
-
- - Available components:
- https://home-assistant.io/components/
-
- - Troubleshooting your configuration:
- https://home-assistant.io/getting-started/troubleshooting-configuration/
-
- - Getting help:
- https://home-assistant.io/help/
-
- This message is generated by the introduction component. You can
- disable it in configuration.yaml.
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- """)
-
- hass.components.persistent_notification.async_create("""
-Here are some resources to get started:
-
- - [Configuring Home Assistant](https://home-assistant.io/getting-started/configuration/)
- - [Available components](https://home-assistant.io/components/)
- - [Troubleshooting your configuration](https://home-assistant.io/docs/configuration/troubleshooting/)
- - [Getting help](https://home-assistant.io/help/)
-
-To not see this card popup in the future, edit your config in
-`configuration.yaml` and disable the `introduction` component.
-""", 'Welcome Home!') # noqa
-
- return True
diff --git a/homeassistant/components/ios/__init__.py b/homeassistant/components/ios/__init__.py
index cc8bd62293a..a9395ed5f5d 100644
--- a/homeassistant/components/ios/__init__.py
+++ b/homeassistant/components/ios/__init__.py
@@ -17,8 +17,6 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'ios'
-DEPENDENCIES = ['device_tracker', 'http', 'zeroconf']
-
CONF_PUSH = 'push'
CONF_PUSH_CATEGORIES = 'categories'
CONF_PUSH_CATEGORIES_NAME = 'name'
diff --git a/homeassistant/components/ios/manifest.json b/homeassistant/components/ios/manifest.json
new file mode 100644
index 00000000000..97c2e2ae28f
--- /dev/null
+++ b/homeassistant/components/ios/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "ios",
+ "name": "Ios",
+ "documentation": "https://www.home-assistant.io/components/ios",
+ "requirements": [],
+ "dependencies": [
+ "device_tracker",
+ "http",
+ "zeroconf"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/ios/notify.py b/homeassistant/components/ios/notify.py
index 1f8aade4ec1..ecbbfb2056c 100644
--- a/homeassistant/components/ios/notify.py
+++ b/homeassistant/components/ios/notify.py
@@ -14,8 +14,6 @@ _LOGGER = logging.getLogger(__name__)
PUSH_URL = "https://ios-push.home-assistant.io/push"
-DEPENDENCIES = ["ios"]
-
# pylint: disable=invalid-name
def log_rate_limits(hass, target, resp, level=20):
diff --git a/homeassistant/components/ios/sensor.py b/homeassistant/components/ios/sensor.py
index 404b313368c..5c5be2b2626 100644
--- a/homeassistant/components/ios/sensor.py
+++ b/homeassistant/components/ios/sensor.py
@@ -3,8 +3,6 @@ from homeassistant.components import ios
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.icon import icon_for_battery_level
-DEPENDENCIES = ['ios']
-
SENSOR_TYPES = {
'level': ['Battery Level', '%'],
'state': ['Battery State', None]
diff --git a/homeassistant/components/iota/__init__.py b/homeassistant/components/iota/__init__.py
index e28de61aad0..c3140e00b97 100644
--- a/homeassistant/components/iota/__init__.py
+++ b/homeassistant/components/iota/__init__.py
@@ -8,8 +8,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyota==2.0.5']
-
_LOGGER = logging.getLogger(__name__)
CONF_IRI = 'iri'
diff --git a/homeassistant/components/iota/manifest.json b/homeassistant/components/iota/manifest.json
new file mode 100644
index 00000000000..d83defbbec3
--- /dev/null
+++ b/homeassistant/components/iota/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "iota",
+ "name": "Iota",
+ "documentation": "https://www.home-assistant.io/components/iota",
+ "requirements": [
+ "pyota==2.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py
index 2955828aff5..c278ab7288d 100644
--- a/homeassistant/components/iota/sensor.py
+++ b/homeassistant/components/iota/sensor.py
@@ -15,8 +15,6 @@ CONF_IRI = 'iri'
CONF_SEED = 'seed'
CONF_TESTNET = 'testnet'
-DEPENDENCIES = ['iota']
-
SCAN_INTERVAL = timedelta(minutes=3)
diff --git a/homeassistant/components/iperf3/__init__.py b/homeassistant/components/iperf3/__init__.py
index 01ac2194f35..00a5738dbd6 100644
--- a/homeassistant/components/iperf3/__init__.py
+++ b/homeassistant/components/iperf3/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
-REQUIREMENTS = ['iperf3==0.1.10']
-
DOMAIN = 'iperf3'
DATA_UPDATED = '{}_data_updated'.format(DOMAIN)
diff --git a/homeassistant/components/iperf3/manifest.json b/homeassistant/components/iperf3/manifest.json
new file mode 100644
index 00000000000..e35be24fc80
--- /dev/null
+++ b/homeassistant/components/iperf3/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "iperf3",
+ "name": "Iperf3",
+ "documentation": "https://www.home-assistant.io/components/iperf3",
+ "requirements": [
+ "iperf3==0.1.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py
index db9aafcdf4b..efc34d8bdef 100644
--- a/homeassistant/components/iperf3/sensor.py
+++ b/homeassistant/components/iperf3/sensor.py
@@ -6,8 +6,6 @@ from homeassistant.helpers.restore_state import RestoreEntity
from . import ATTR_VERSION, DATA_UPDATED, DOMAIN as IPERF3_DOMAIN, SENSOR_TYPES
-DEPENDENCIES = ['iperf3']
-
ATTRIBUTION = 'Data retrieved using Iperf3'
ICON = 'mdi:speedometer'
diff --git a/homeassistant/components/ipma/.translations/fr.json b/homeassistant/components/ipma/.translations/fr.json
index 058908db36b..1ca5353ec7e 100644
--- a/homeassistant/components/ipma/.translations/fr.json
+++ b/homeassistant/components/ipma/.translations/fr.json
@@ -5,6 +5,11 @@
},
"step": {
"user": {
+ "data": {
+ "latitude": "Latitude",
+ "longitude": "Longitude",
+ "name": "Nom"
+ },
"title": "Emplacement"
}
},
diff --git a/homeassistant/components/ipma/manifest.json b/homeassistant/components/ipma/manifest.json
new file mode 100644
index 00000000000..29fc0429e86
--- /dev/null
+++ b/homeassistant/components/ipma/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ipma",
+ "name": "Ipma",
+ "documentation": "https://www.home-assistant.io/components/ipma",
+ "requirements": [
+ "pyipma==1.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/ipma/weather.py b/homeassistant/components/ipma/weather.py
index 7122957ad12..e976bcb9896 100644
--- a/homeassistant/components/ipma/weather.py
+++ b/homeassistant/components/ipma/weather.py
@@ -15,8 +15,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyipma==1.2.1']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Instituto Português do Mar e Atmosfera'
diff --git a/homeassistant/components/irish_rail_transport/manifest.json b/homeassistant/components/irish_rail_transport/manifest.json
new file mode 100644
index 00000000000..5961400e68e
--- /dev/null
+++ b/homeassistant/components/irish_rail_transport/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "irish_rail_transport",
+ "name": "Irish rail transport",
+ "documentation": "https://www.home-assistant.io/components/irish_rail_transport",
+ "requirements": [
+ "pyirishrail==0.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ttroy50"
+ ]
+}
diff --git a/homeassistant/components/irish_rail_transport/sensor.py b/homeassistant/components/irish_rail_transport/sensor.py
index e17ecfde59d..586568ca9ef 100644
--- a/homeassistant/components/irish_rail_transport/sensor.py
+++ b/homeassistant/components/irish_rail_transport/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyirishrail==0.0.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_STATION = "Station"
diff --git a/homeassistant/components/islamic_prayer_times/manifest.json b/homeassistant/components/islamic_prayer_times/manifest.json
new file mode 100644
index 00000000000..4dc9e2cb7c3
--- /dev/null
+++ b/homeassistant/components/islamic_prayer_times/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "islamic_prayer_times",
+ "name": "Islamic prayer times",
+ "documentation": "https://www.home-assistant.io/components/islamic_prayer_times",
+ "requirements": [
+ "prayer_times_calculator==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/islamic_prayer_times/sensor.py b/homeassistant/components/islamic_prayer_times/sensor.py
index 50331435491..c50c01c2ece 100644
--- a/homeassistant/components/islamic_prayer_times/sensor.py
+++ b/homeassistant/components/islamic_prayer_times/sensor.py
@@ -1,9 +1,4 @@
-"""
-Platform to retrieve Islamic prayer times information for Home Assistant.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.islamic_prayer_times/
-"""
+"""Platform to retrieve Islamic prayer times information for Home Assistant."""
import logging
from datetime import datetime, timedelta
@@ -16,8 +11,6 @@ from homeassistant.const import DEVICE_CLASS_TIMESTAMP
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_time
-REQUIREMENTS = ['prayer_times_calculator==0.0.3']
-
_LOGGER = logging.getLogger(__name__)
PRAYER_TIMES_ICON = 'mdi:calendar-clock'
diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py
index b986c51ddaa..97e5087819e 100644
--- a/homeassistant/components/iss/binary_sensor.py
+++ b/homeassistant/components/iss/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for International Space Station data sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.iss/
-"""
+"""Support for International Space Station data sensor."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.const import (
CONF_NAME, ATTR_LONGITUDE, ATTR_LATITUDE, CONF_SHOW_ON_MAP)
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyiss==1.0.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ISS_NEXT_RISE = 'next_rise'
diff --git a/homeassistant/components/iss/manifest.json b/homeassistant/components/iss/manifest.json
new file mode 100644
index 00000000000..dc71e81ac08
--- /dev/null
+++ b/homeassistant/components/iss/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "iss",
+ "name": "Iss",
+ "documentation": "https://www.home-assistant.io/components/iss",
+ "requirements": [
+ "pyiss==1.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py
index 4eaa71deece..de5e09f6238 100644
--- a/homeassistant/components/isy994/__init__.py
+++ b/homeassistant/components/isy994/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, Dict
-REQUIREMENTS = ['PyISY==1.1.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'isy994'
diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json
new file mode 100644
index 00000000000..7860c080b2f
--- /dev/null
+++ b/homeassistant/components/isy994/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "isy994",
+ "name": "Isy994",
+ "documentation": "https://www.home-assistant.io/components/isy994",
+ "requirements": [
+ "PyISY==1.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/itach/manifest.json b/homeassistant/components/itach/manifest.json
new file mode 100644
index 00000000000..c26b19c636e
--- /dev/null
+++ b/homeassistant/components/itach/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "itach",
+ "name": "Itach",
+ "documentation": "https://www.home-assistant.io/components/itach",
+ "requirements": [
+ "pyitachip2ir==0.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/itach/remote.py b/homeassistant/components/itach/remote.py
index beb773838fb..54dfa1fcfb9 100644
--- a/homeassistant/components/itach/remote.py
+++ b/homeassistant/components/itach/remote.py
@@ -10,8 +10,6 @@ from homeassistant.const import (
CONF_DEVICES)
from homeassistant.components.remote import PLATFORM_SCHEMA
-REQUIREMENTS = ['pyitachip2ir==0.0.7']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_PORT = 4998
diff --git a/homeassistant/components/itunes/manifest.json b/homeassistant/components/itunes/manifest.json
new file mode 100644
index 00000000000..6f05125661e
--- /dev/null
+++ b/homeassistant/components/itunes/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "itunes",
+ "name": "Itunes",
+ "documentation": "https://www.home-assistant.io/components/itunes",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/itunes/media_player.py b/homeassistant/components/itunes/media_player.py
index f8380032aea..8451d751954 100644
--- a/homeassistant/components/itunes/media_player.py
+++ b/homeassistant/components/itunes/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing to iTunes API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.itunes/
-"""
+"""Support for interfacing to iTunes API."""
import logging
import requests
diff --git a/homeassistant/components/jewish_calendar/manifest.json b/homeassistant/components/jewish_calendar/manifest.json
new file mode 100644
index 00000000000..1f2917865b3
--- /dev/null
+++ b/homeassistant/components/jewish_calendar/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "jewish_calendar",
+ "name": "Jewish calendar",
+ "documentation": "https://www.home-assistant.io/components/jewish_calendar",
+ "requirements": [
+ "hdate==0.8.7"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@tsvi"
+ ]
+}
diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py
index 65aeaf7fba9..ec86abecc44 100644
--- a/homeassistant/components/jewish_calendar/sensor.py
+++ b/homeassistant/components/jewish_calendar/sensor.py
@@ -1,9 +1,4 @@
-"""
-Platform to retrieve Jewish calendar information for Home Assistant.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.jewish_calendar/
-"""
+"""Platform to retrieve Jewish calendar information for Home Assistant."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.sun import get_astral_event_date
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['hdate==0.8.7']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py
index f1371deed2b..4a3cf737c96 100644
--- a/homeassistant/components/joaoapps_join/__init__.py
+++ b/homeassistant/components/joaoapps_join/__init__.py
@@ -6,8 +6,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME, CONF_API_KEY
-REQUIREMENTS = ['python-join-api==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'joaoapps_join'
diff --git a/homeassistant/components/joaoapps_join/manifest.json b/homeassistant/components/joaoapps_join/manifest.json
new file mode 100644
index 00000000000..220f2af2035
--- /dev/null
+++ b/homeassistant/components/joaoapps_join/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "joaoapps_join",
+ "name": "Joaoapps join",
+ "documentation": "https://www.home-assistant.io/components/joaoapps_join",
+ "requirements": [
+ "python-join-api==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py
index 0137520049d..d9eabce5476 100644
--- a/homeassistant/components/joaoapps_join/notify.py
+++ b/homeassistant/components/joaoapps_join/notify.py
@@ -7,8 +7,6 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-join-api==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEVICE_ID = 'device_id'
diff --git a/homeassistant/components/joaoapps_join/services.yaml b/homeassistant/components/joaoapps_join/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py
index f62331d1502..919322487b1 100644
--- a/homeassistant/components/juicenet/__init__.py
+++ b/homeassistant/components/juicenet/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-juicenet==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'juicenet'
diff --git a/homeassistant/components/juicenet/manifest.json b/homeassistant/components/juicenet/manifest.json
new file mode 100644
index 00000000000..e65aab2b69d
--- /dev/null
+++ b/homeassistant/components/juicenet/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "juicenet",
+ "name": "Juicenet",
+ "documentation": "https://www.home-assistant.io/components/juicenet",
+ "requirements": [
+ "python-juicenet==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py
index 6b55e539547..60369b1f92a 100644
--- a/homeassistant/components/juicenet/sensor.py
+++ b/homeassistant/components/juicenet/sensor.py
@@ -8,8 +8,6 @@ from . import DOMAIN, JuicenetDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['juicenet']
-
SENSOR_TYPES = {
'status': ['Charging Status', None],
'temperature': ['Temperature', TEMP_CELSIUS],
diff --git a/homeassistant/components/kankun/manifest.json b/homeassistant/components/kankun/manifest.json
new file mode 100644
index 00000000000..8e4e9747901
--- /dev/null
+++ b/homeassistant/components/kankun/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "kankun",
+ "name": "Kankun",
+ "documentation": "https://www.home-assistant.io/components/kankun",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/kankun/switch.py b/homeassistant/components/kankun/switch.py
index 86e7fcdab3e..a8282a366ad 100644
--- a/homeassistant/components/kankun/switch.py
+++ b/homeassistant/components/kankun/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for customised Kankun SP3 Wifi switch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.kankun/
-"""
+"""Support for customised Kankun SP3 Wifi switch."""
import logging
import requests
diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py
index b8c2124ff0f..e52dff7476d 100644
--- a/homeassistant/components/keenetic_ndms2/device_tracker.py
+++ b/homeassistant/components/keenetic_ndms2/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Zyxel Keenetic NDMS2 based routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.keenetic_ndms2/
-"""
+"""Support for Zyxel Keenetic NDMS2 based routers."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_PASSWORD, CONF_USERNAME
)
-REQUIREMENTS = ['ndms2_client==0.0.6']
-
_LOGGER = logging.getLogger(__name__)
# Interface name to track devices for. Most likely one will not need to
diff --git a/homeassistant/components/keenetic_ndms2/manifest.json b/homeassistant/components/keenetic_ndms2/manifest.json
new file mode 100644
index 00000000000..d95e6384606
--- /dev/null
+++ b/homeassistant/components/keenetic_ndms2/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "keenetic_ndms2",
+ "name": "Keenetic ndms2",
+ "documentation": "https://www.home-assistant.io/components/keenetic_ndms2",
+ "requirements": [
+ "ndms2_client==0.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/keyboard/__init__.py b/homeassistant/components/keyboard/__init__.py
index 44accca2f56..f841e7e9681 100644
--- a/homeassistant/components/keyboard/__init__.py
+++ b/homeassistant/components/keyboard/__init__.py
@@ -6,8 +6,6 @@ from homeassistant.const import (
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_UP)
-REQUIREMENTS = ['pyuserinput==0.1.11']
-
DOMAIN = 'keyboard'
TAP_KEY_SCHEMA = vol.Schema({})
diff --git a/homeassistant/components/keyboard/manifest.json b/homeassistant/components/keyboard/manifest.json
new file mode 100644
index 00000000000..0e8ade339c2
--- /dev/null
+++ b/homeassistant/components/keyboard/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "keyboard",
+ "name": "Keyboard",
+ "documentation": "https://www.home-assistant.io/components/keyboard",
+ "requirements": [
+ "pyuserinput==0.1.11"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/keyboard/services.yaml b/homeassistant/components/keyboard/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py
index e786fe458a8..71df70f51f0 100644
--- a/homeassistant/components/keyboard_remote/__init__.py
+++ b/homeassistant/components/keyboard_remote/__init__.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['evdev==0.6.1']
-
_LOGGER = logging.getLogger(__name__)
DEVICE_DESCRIPTOR = 'device_descriptor'
diff --git a/homeassistant/components/keyboard_remote/manifest.json b/homeassistant/components/keyboard_remote/manifest.json
new file mode 100644
index 00000000000..d87d1abca48
--- /dev/null
+++ b/homeassistant/components/keyboard_remote/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "keyboard_remote",
+ "name": "Keyboard remote",
+ "documentation": "https://www.home-assistant.io/components/keyboard_remote",
+ "requirements": [
+ "evdev==0.6.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/kira/__init__.py b/homeassistant/components/kira/__init__.py
index d60d8e0cfeb..7cf27d342f5 100644
--- a/homeassistant/components/kira/__init__.py
+++ b/homeassistant/components/kira/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pykira==0.1.1']
-
DOMAIN = 'kira'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/kira/manifest.json b/homeassistant/components/kira/manifest.json
new file mode 100644
index 00000000000..b7edd1f6c5f
--- /dev/null
+++ b/homeassistant/components/kira/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "kira",
+ "name": "Kira",
+ "documentation": "https://www.home-assistant.io/components/kira",
+ "requirements": [
+ "pykira==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py
index 5d217796767..bbeb2dce04a 100644
--- a/homeassistant/components/kiwi/lock.py
+++ b/homeassistant/components/kiwi/lock.py
@@ -1,9 +1,4 @@
-"""
-Support for the KIWI.KI lock platform.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/lock.kiwi/
-"""
+"""Support for the KIWI.KI lock platform."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.const import (
from homeassistant.helpers.event import async_call_later
from homeassistant.core import callback
-REQUIREMENTS = ['kiwiki-client==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_TYPE = 'hardware_type'
diff --git a/homeassistant/components/kiwi/manifest.json b/homeassistant/components/kiwi/manifest.json
new file mode 100644
index 00000000000..9f1595ebd77
--- /dev/null
+++ b/homeassistant/components/kiwi/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "kiwi",
+ "name": "Kiwi",
+ "documentation": "https://www.home-assistant.io/components/kiwi",
+ "requirements": [
+ "kiwiki-client==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py
index ea5b18b7ede..04b51730be1 100644
--- a/homeassistant/components/knx/__init__.py
+++ b/homeassistant/components/knx/__init__.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.script import Script
-REQUIREMENTS = ['xknx==0.10.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = "knx"
diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py
index 8ee21e24c5e..65d10722500 100644
--- a/homeassistant/components/knx/binary_sensor.py
+++ b/homeassistant/components/knx/binary_sensor.py
@@ -22,8 +22,6 @@ CONF_RESET_AFTER = 'reset_after'
CONF__ACTION = 'turn_off_action'
DEFAULT_NAME = 'KNX Binary Sensor'
-DEPENDENCIES = ['knx']
-
AUTOMATION_SCHEMA = vol.Schema({
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py
index e11e5449326..f4835389dfa 100644
--- a/homeassistant/components/knx/climate.py
+++ b/homeassistant/components/knx/climate.py
@@ -39,8 +39,6 @@ DEFAULT_NAME = 'KNX Climate'
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
-DEPENDENCIES = ['knx']
-
# Map KNX operation modes to HA modes. This list might not be full.
OPERATION_MODES = {
# Map DPT 201.100 HVAC operating modes
diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py
index b2b287d1e87..bbee54e00cd 100644
--- a/homeassistant/components/knx/cover.py
+++ b/homeassistant/components/knx/cover.py
@@ -25,8 +25,6 @@ CONF_INVERT_ANGLE = 'invert_angle'
DEFAULT_TRAVEL_TIME = 25
DEFAULT_NAME = 'KNX Cover'
-DEPENDENCIES = ['knx']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string,
diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py
index cf59f1fc135..b94d91514af 100644
--- a/homeassistant/components/knx/light.py
+++ b/homeassistant/components/knx/light.py
@@ -30,7 +30,6 @@ DEFAULT_BRIGHTNESS = 255
DEFAULT_COLOR_TEMP_MODE = 'absolute'
DEFAULT_MIN_KELVIN = 2700 # 370 mireds
DEFAULT_MAX_KELVIN = 6000 # 166 mireds
-DEPENDENCIES = ['knx']
class ColorTempModes(Enum):
diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json
new file mode 100644
index 00000000000..1b1f16ccb03
--- /dev/null
+++ b/homeassistant/components/knx/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "knx",
+ "name": "Knx",
+ "documentation": "https://www.home-assistant.io/components/knx",
+ "requirements": [
+ "xknx==0.10.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@Julius2342"
+ ]
+}
diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py
index 742252d1874..486908c3cff 100644
--- a/homeassistant/components/knx/notify.py
+++ b/homeassistant/components/knx/notify.py
@@ -11,8 +11,6 @@ from . import ATTR_DISCOVER_DEVICES, DATA_KNX
DEFAULT_NAME = 'KNX Notify'
-DEPENDENCIES = ['knx']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py
index 4bf186c28ff..4f0c7b2d4fc 100644
--- a/homeassistant/components/knx/scene.py
+++ b/homeassistant/components/knx/scene.py
@@ -11,8 +11,6 @@ from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_SCENE_NUMBER = 'scene_number'
DEFAULT_NAME = 'KNX SCENE'
-DEPENDENCIES = ['knx']
-
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'knx',
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py
index 7ddafe53be4..bb3128eaee7 100644
--- a/homeassistant/components/knx/sensor.py
+++ b/homeassistant/components/knx/sensor.py
@@ -10,8 +10,6 @@ from homeassistant.helpers.entity import Entity
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
DEFAULT_NAME = 'KNX Sensor'
-DEPENDENCIES = ['knx']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py
index e3beff39677..461b27e94c0 100644
--- a/homeassistant/components/knx/switch.py
+++ b/homeassistant/components/knx/switch.py
@@ -11,8 +11,6 @@ from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_STATE_ADDRESS = 'state_address'
DEFAULT_NAME = 'KNX Switch'
-DEPENDENCIES = ['knx']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
diff --git a/homeassistant/components/kodi/manifest.json b/homeassistant/components/kodi/manifest.json
new file mode 100644
index 00000000000..8c684d495e9
--- /dev/null
+++ b/homeassistant/components/kodi/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "kodi",
+ "name": "Kodi",
+ "documentation": "https://www.home-assistant.io/components/kodi",
+ "requirements": [
+ "jsonrpc-async==0.6",
+ "jsonrpc-websocket==0.6"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@armills"
+ ]
+}
diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py
index f8d0cdc5a12..96fb02a08a0 100644
--- a/homeassistant/components/kodi/media_player.py
+++ b/homeassistant/components/kodi/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing with the XBMC/Kodi JSON-RPC API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.kodi/
-"""
+"""Support for interfacing with the XBMC/Kodi JSON-RPC API."""
import asyncio
from collections import OrderedDict
from functools import wraps
@@ -36,8 +31,6 @@ from homeassistant.helpers.template import Template
from homeassistant.util.yaml import dump
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['jsonrpc-async==0.6', 'jsonrpc-websocket==0.6']
-
_LOGGER = logging.getLogger(__name__)
EVENT_KODI_CALL_METHOD_RESULT = 'kodi_call_method_result'
diff --git a/homeassistant/components/kodi/notify.py b/homeassistant/components/kodi/notify.py
index 7c2a60f3498..fb5326c83c8 100644
--- a/homeassistant/components/kodi/notify.py
+++ b/homeassistant/components/kodi/notify.py
@@ -1,9 +1,4 @@
-"""
-Kodi notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.kodi/
-"""
+"""Kodi notification service."""
import logging
import aiohttp
@@ -19,8 +14,6 @@ from homeassistant.components.notify import (
ATTR_DATA, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['jsonrpc-async==0.6']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_PORT = 8080
diff --git a/homeassistant/components/kodi/services.yaml b/homeassistant/components/kodi/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py
index 276e395817c..ee4ba16e54c 100644
--- a/homeassistant/components/konnected/__init__.py
+++ b/homeassistant/components/konnected/__init__.py
@@ -32,8 +32,6 @@ from .handlers import HANDLERS
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['konnected==0.1.5']
-
_BINARY_SENSOR_SCHEMA = vol.All(
vol.Schema({
vol.Exclusive(CONF_PIN, 's_pin'): vol.Any(*PIN_TO_ZONE),
@@ -96,8 +94,6 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
-DEPENDENCIES = ['http']
-
async def async_setup(hass, config):
"""Set up the Konnected platform."""
diff --git a/homeassistant/components/konnected/binary_sensor.py b/homeassistant/components/konnected/binary_sensor.py
index 1fbfbea1861..3abd9be6c4b 100644
--- a/homeassistant/components/konnected/binary_sensor.py
+++ b/homeassistant/components/konnected/binary_sensor.py
@@ -12,8 +12,6 @@ from . import DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, SIGNAL_SENSOR_UPDATE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['konnected']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/konnected/manifest.json b/homeassistant/components/konnected/manifest.json
new file mode 100644
index 00000000000..e4129af39bd
--- /dev/null
+++ b/homeassistant/components/konnected/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "konnected",
+ "name": "Konnected",
+ "documentation": "https://www.home-assistant.io/components/konnected",
+ "requirements": [
+ "konnected==0.1.5"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@heythisisnate"
+ ]
+}
diff --git a/homeassistant/components/konnected/sensor.py b/homeassistant/components/konnected/sensor.py
index a48d1a58619..7881eacff40 100644
--- a/homeassistant/components/konnected/sensor.py
+++ b/homeassistant/components/konnected/sensor.py
@@ -13,8 +13,6 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['konnected']
-
SENSOR_TYPES = {
DEVICE_CLASS_TEMPERATURE: ['Temperature', TEMP_CELSIUS],
DEVICE_CLASS_HUMIDITY: ['Humidity', '%']
diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py
index 3db602215b9..841e84e2487 100644
--- a/homeassistant/components/konnected/switch.py
+++ b/homeassistant/components/konnected/switch.py
@@ -11,8 +11,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['konnected']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/kwb/manifest.json b/homeassistant/components/kwb/manifest.json
new file mode 100644
index 00000000000..783907c0220
--- /dev/null
+++ b/homeassistant/components/kwb/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "kwb",
+ "name": "Kwb",
+ "documentation": "https://www.home-assistant.io/components/kwb",
+ "requirements": [
+ "pykwb==0.0.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/kwb/sensor.py b/homeassistant/components/kwb/sensor.py
index f490fbd5b14..7a153970d18 100644
--- a/homeassistant/components/kwb/sensor.py
+++ b/homeassistant/components/kwb/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for KWB Easyfire.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.kwb/
-"""
+"""Support for KWB Easyfire."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pykwb==0.0.8']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_RAW = False
diff --git a/homeassistant/components/lacrosse/manifest.json b/homeassistant/components/lacrosse/manifest.json
new file mode 100644
index 00000000000..4716b3cb548
--- /dev/null
+++ b/homeassistant/components/lacrosse/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lacrosse",
+ "name": "Lacrosse",
+ "documentation": "https://www.home-assistant.io/components/lacrosse",
+ "requirements": [
+ "pylacrosse==0.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py
index 32b1dac9250..dea51b0c917 100644
--- a/homeassistant/components/lacrosse/sensor.py
+++ b/homeassistant/components/lacrosse/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for LaCrosse sensor components.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.lacrosse/
-"""
+"""Support for LaCrosse sensor components."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util
-REQUIREMENTS = ['pylacrosse==0.3.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_BAUD = 'baud'
diff --git a/homeassistant/components/lametric/__init__.py b/homeassistant/components/lametric/__init__.py
index 0c3c8b08dd7..057594f42ae 100644
--- a/homeassistant/components/lametric/__init__.py
+++ b/homeassistant/components/lametric/__init__.py
@@ -5,8 +5,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['lmnotify==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
diff --git a/homeassistant/components/lametric/manifest.json b/homeassistant/components/lametric/manifest.json
new file mode 100644
index 00000000000..bbf22918a75
--- /dev/null
+++ b/homeassistant/components/lametric/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "lametric",
+ "name": "Lametric",
+ "documentation": "https://www.home-assistant.io/components/lametric",
+ "requirements": [
+ "lmnotify==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/lametric/notify.py b/homeassistant/components/lametric/notify.py
index 358bb056b00..92b254cd2b0 100644
--- a/homeassistant/components/lametric/notify.py
+++ b/homeassistant/components/lametric/notify.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN as LAMETRIC_DOMAIN
-REQUIREMENTS = ['lmnotify==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
AVAILABLE_PRIORITIES = ['info', 'warning', 'critical']
@@ -21,8 +19,6 @@ CONF_CYCLES = 'cycles'
CONF_LIFETIME = 'lifetime'
CONF_PRIORITY = 'priority'
-DEPENDENCIES = ['lametric']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ICON, default='a7956'): cv.string,
vol.Optional(CONF_LIFETIME, default=10): cv.positive_int,
diff --git a/homeassistant/components/lannouncer/manifest.json b/homeassistant/components/lannouncer/manifest.json
new file mode 100644
index 00000000000..951dd3ff85b
--- /dev/null
+++ b/homeassistant/components/lannouncer/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "lannouncer",
+ "name": "Lannouncer",
+ "documentation": "https://www.home-assistant.io/components/lannouncer",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lannouncer/notify.py b/homeassistant/components/lannouncer/notify.py
index 3b2e72b398c..535940a80f5 100644
--- a/homeassistant/components/lannouncer/notify.py
+++ b/homeassistant/components/lannouncer/notify.py
@@ -1,9 +1,4 @@
-"""
-Lannouncer platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.lannouncer/
-"""
+"""Lannouncer platform for notify component."""
import logging
import socket
from urllib.parse import urlencode
diff --git a/homeassistant/components/lastfm/manifest.json b/homeassistant/components/lastfm/manifest.json
new file mode 100644
index 00000000000..2617b3e206b
--- /dev/null
+++ b/homeassistant/components/lastfm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lastfm",
+ "name": "Lastfm",
+ "documentation": "https://www.home-assistant.io/components/lastfm",
+ "requirements": [
+ "pylast==3.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py
index e4e28eff4f1..32774b1bf28 100644
--- a/homeassistant/components/lastfm/sensor.py
+++ b/homeassistant/components/lastfm/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_API_KEY, ATTR_ATTRIBUTION
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pylast==3.1.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_LAST_PLAYED = 'last_played'
diff --git a/homeassistant/components/launch_library/manifest.json b/homeassistant/components/launch_library/manifest.json
new file mode 100644
index 00000000000..bbe9fa8ad05
--- /dev/null
+++ b/homeassistant/components/launch_library/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "launch_library",
+ "name": "Launch library",
+ "documentation": "https://www.home-assistant.io/components/launch_library",
+ "requirements": [
+ "pylaunches==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py
index aaed2f9663f..a1c5b5825a9 100644
--- a/homeassistant/components/launch_library/sensor.py
+++ b/homeassistant/components/launch_library/sensor.py
@@ -1,9 +1,4 @@
-"""
-A sensor platform that give you information about the next space launch.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/sensor.launch_library/
-"""
+"""A sensor platform that give you information about the next space launch."""
from datetime import timedelta
import logging
@@ -15,8 +10,6 @@ from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['pylaunches==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Launch Library."
diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py
index e380c2bb4a1..418b6ffa89d 100644
--- a/homeassistant/components/lcn/__init__.py
+++ b/homeassistant/components/lcn/__init__.py
@@ -4,24 +4,22 @@ import logging
import voluptuous as vol
from homeassistant.const import (
- CONF_ADDRESS, CONF_COVERS, CONF_HOST, CONF_LIGHTS, CONF_NAME,
- CONF_PASSWORD, CONF_PORT, CONF_SENSORS, CONF_SWITCHES,
+ CONF_ADDRESS, CONF_BINARY_SENSORS, CONF_COVERS, CONF_HOST, CONF_LIGHTS,
+ CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SENSORS, CONF_SWITCHES,
CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.entity import Entity
from .const import (
- CONF_CONNECTIONS, CONF_DIM_MODE, CONF_DIMMABLE, CONF_MOTOR, CONF_OUTPUT,
- CONF_SK_NUM_TRIES, CONF_SOURCE, CONF_TRANSITION, DATA_LCN, DEFAULT_NAME,
- DIM_MODES, DOMAIN, LED_PORTS, LOGICOP_PORTS, MOTOR_PORTS, OUTPUT_PORTS,
- PATTERN_ADDRESS, RELAY_PORTS, S0_INPUTS, SETPOINTS, THRESHOLDS, VAR_UNITS,
- VARIABLES)
+ BINSENSOR_PORTS, CONF_CONNECTIONS, CONF_DIM_MODE, CONF_DIMMABLE,
+ CONF_MOTOR, CONF_OUTPUT, CONF_SK_NUM_TRIES, CONF_SOURCE, CONF_TRANSITION,
+ DATA_LCN, DEFAULT_NAME, DIM_MODES, DOMAIN, KEYS, LED_PORTS, LOGICOP_PORTS,
+ MOTOR_PORTS, OUTPUT_PORTS, PATTERN_ADDRESS, RELAY_PORTS, S0_INPUTS,
+ SETPOINTS, THRESHOLDS, VAR_UNITS, VARIABLES)
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pypck==0.5.9']
-
def has_unique_connection_names(connections):
"""Validate that all connection names are unique.
@@ -65,6 +63,13 @@ def is_address(value):
raise vol.error.Invalid('Not a valid address string.')
+BINARY_SENSORS_SCHEMA = vol.Schema({
+ vol.Required(CONF_NAME): cv.string,
+ vol.Required(CONF_ADDRESS): is_address,
+ vol.Required(CONF_SOURCE): vol.All(vol.Upper, vol.In(SETPOINTS + KEYS +
+ BINSENSOR_PORTS))
+ })
+
COVERS_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_ADDRESS): is_address,
@@ -115,6 +120,8 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_CONNECTIONS): vol.All(
cv.ensure_list, has_unique_connection_names, [CONNECTION_SCHEMA]),
+ vol.Optional(CONF_BINARY_SENSORS): vol.All(
+ cv.ensure_list, [BINARY_SENSORS_SCHEMA]),
vol.Optional(CONF_COVERS): vol.All(
cv.ensure_list, [COVERS_SCHEMA]),
vol.Optional(CONF_LIGHTS): vol.All(
@@ -177,7 +184,8 @@ async def async_setup(hass, config):
hass.data[DATA_LCN][CONF_CONNECTIONS] = connections
# load platforms
- for component, conf_key in (('cover', CONF_COVERS),
+ for component, conf_key in (('binary_sensor', CONF_BINARY_SENSORS),
+ ('cover', CONF_COVERS),
('light', CONF_LIGHTS),
('sensor', CONF_SENSORS),
('switch', CONF_SWITCHES)):
diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py
new file mode 100755
index 00000000000..ec37d3e5128
--- /dev/null
+++ b/homeassistant/components/lcn/binary_sensor.py
@@ -0,0 +1,137 @@
+"""Support for LCN binary sensors."""
+from homeassistant.components.binary_sensor import BinarySensorDevice
+from homeassistant.const import CONF_ADDRESS
+
+from . import LcnDevice, get_connection
+from .const import (
+ BINSENSOR_PORTS, CONF_CONNECTIONS, CONF_SOURCE, DATA_LCN, SETPOINTS)
+
+
+async def async_setup_platform(hass, hass_config, async_add_entities,
+ discovery_info=None):
+ """Set up the LCN binary sensor platform."""
+ if discovery_info is None:
+ return
+
+ import pypck
+
+ devices = []
+ for config in discovery_info:
+ address, connection_id = config[CONF_ADDRESS]
+ addr = pypck.lcn_addr.LcnAddr(*address)
+ connections = hass.data[DATA_LCN][CONF_CONNECTIONS]
+ connection = get_connection(connections, connection_id)
+ address_connection = connection.get_address_conn(addr)
+
+ if config[CONF_SOURCE] in SETPOINTS:
+ device = LcnRegulatorLockSensor(config, address_connection)
+ elif config[CONF_SOURCE] in BINSENSOR_PORTS:
+ device = LcnBinarySensor(config, address_connection)
+ else: # in KEYS
+ device = LcnLockKeysSensor(config, address_connection)
+
+ devices.append(device)
+
+ async_add_entities(devices)
+
+
+class LcnRegulatorLockSensor(LcnDevice, BinarySensorDevice):
+ """Representation of a LCN binary sensor for regulator locks."""
+
+ def __init__(self, config, address_connection):
+ """Initialize the LCN binary sensor."""
+ super().__init__(config, address_connection)
+
+ self.setpoint_variable = \
+ self.pypck.lcn_defs.Var[config[CONF_SOURCE]]
+
+ self._value = None
+
+ async def async_added_to_hass(self):
+ """Run when entity about to be added to hass."""
+ await super().async_added_to_hass()
+ self.hass.async_create_task(
+ self.address_connection.activate_status_request_handler(
+ self.setpoint_variable))
+
+ @property
+ def is_on(self):
+ """Return true if the binary sensor is on."""
+ return self._value
+
+ def input_received(self, input_obj):
+ """Set sensor value when LCN input object (command) is received."""
+ if not isinstance(input_obj, self.pypck.inputs.ModStatusVar) or \
+ input_obj.get_var() != self.setpoint_variable:
+ return
+
+ self._value = input_obj.get_value().is_locked_regulator()
+ self.async_schedule_update_ha_state()
+
+
+class LcnBinarySensor(LcnDevice, BinarySensorDevice):
+ """Representation of a LCN binary sensor for binary sensor ports."""
+
+ def __init__(self, config, address_connection):
+ """Initialize the LCN binary sensor."""
+ super().__init__(config, address_connection)
+
+ self.bin_sensor_port = \
+ self.pypck.lcn_defs.BinSensorPort[config[CONF_SOURCE]]
+
+ self._value = None
+
+ async def async_added_to_hass(self):
+ """Run when entity about to be added to hass."""
+ await super().async_added_to_hass()
+ self.hass.async_create_task(
+ self.address_connection.activate_status_request_handler(
+ self.bin_sensor_port))
+
+ @property
+ def is_on(self):
+ """Return true if the binary sensor is on."""
+ return self._value
+
+ def input_received(self, input_obj):
+ """Set sensor value when LCN input object (command) is received."""
+ if not isinstance(input_obj, self.pypck.inputs.ModStatusBinSensors):
+ return
+
+ self._value = input_obj.get_state(self.bin_sensor_port.value)
+ self.async_schedule_update_ha_state()
+
+
+class LcnLockKeysSensor(LcnDevice, BinarySensorDevice):
+ """Representation of a LCN sensor for key locks."""
+
+ def __init__(self, config, address_connection):
+ """Initialize the LCN sensor."""
+ super().__init__(config, address_connection)
+
+ self.source = self.pypck.lcn_defs.Key[config[CONF_SOURCE]]
+ self._value = None
+
+ async def async_added_to_hass(self):
+ """Run when entity about to be added to hass."""
+ await super().async_added_to_hass()
+ self.hass.async_create_task(
+ self.address_connection.activate_status_request_handler(
+ self.source))
+
+ @property
+ def is_on(self):
+ """Return true if the binary sensor is on."""
+ return self._value
+
+ def input_received(self, input_obj):
+ """Set sensor value when LCN input object (command) is received."""
+ if not isinstance(input_obj, self.pypck.inputs.ModStatusKeyLocks) or \
+ self.source not in self.pypck.lcn_defs.Key:
+ return
+
+ table_id = ord(self.source.name[0]) - 65
+ key_id = int(self.source.name[1]) - 1
+
+ self._value = input_obj.get_state(table_id, key_id)
+ self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py
index ee7a3a79cde..b745d0636c2 100644
--- a/homeassistant/components/lcn/const.py
+++ b/homeassistant/components/lcn/const.py
@@ -1,5 +1,6 @@
# coding: utf-8
"""Constants for the LCN component."""
+from itertools import product
import re
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
@@ -37,6 +38,12 @@ LED_PORTS = ['LED1', 'LED2', 'LED3', 'LED4', 'LED5', 'LED6',
LOGICOP_PORTS = ['LOGICOP1', 'LOGICOP2', 'LOGICOP3', 'LOGICOP4']
+BINSENSOR_PORTS = ['BINSENSOR1', 'BINSENSOR2', 'BINSENSOR3', 'BINSENSOR4',
+ 'BINSENSOR5', 'BINSENSOR6', 'BINSENSOR7', 'BINSENSOR8']
+
+KEYS = ['{:s}{:d}'.format(t[0], t[1]) for t in product(['A', 'B', 'C', 'D'],
+ range(1, 9))]
+
VARIABLES = ['VAR1ORTVAR', 'VAR2ORR1VAR', 'VAR3ORR2VAR',
'TVAR', 'R1VAR', 'R2VAR',
'VAR1', 'VAR2', 'VAR3', 'VAR4', 'VAR5', 'VAR6',
diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py
index a32ff7c23f4..7123f2d5d0a 100755
--- a/homeassistant/components/lcn/cover.py
+++ b/homeassistant/components/lcn/cover.py
@@ -5,8 +5,6 @@ from homeassistant.const import CONF_ADDRESS
from . import LcnDevice, get_connection
from .const import CONF_CONNECTIONS, CONF_MOTOR, DATA_LCN
-DEPENDENCIES = ['lcn']
-
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py
index 00b78259354..653873ba78a 100644
--- a/homeassistant/components/lcn/light.py
+++ b/homeassistant/components/lcn/light.py
@@ -9,8 +9,6 @@ from .const import (
CONF_CONNECTIONS, CONF_DIMMABLE, CONF_OUTPUT, CONF_TRANSITION, DATA_LCN,
OUTPUT_PORTS)
-DEPENDENCIES = ['lcn']
-
async def async_setup_platform(
hass, hass_config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json
new file mode 100644
index 00000000000..bbf2746c067
--- /dev/null
+++ b/homeassistant/components/lcn/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lcn",
+ "name": "Lcn",
+ "documentation": "https://www.home-assistant.io/components/lcn",
+ "requirements": [
+ "pypck==0.5.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py
index 5e50d092ada..48ac8c7266c 100755
--- a/homeassistant/components/lcn/sensor.py
+++ b/homeassistant/components/lcn/sensor.py
@@ -6,8 +6,6 @@ from .const import (
CONF_CONNECTIONS, CONF_SOURCE, DATA_LCN, LED_PORTS, S0_INPUTS, SETPOINTS,
THRESHOLDS, VARIABLES)
-DEPENDENCIES = ['lcn']
-
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py
index 7c375f4a598..48ae579fbcd 100755
--- a/homeassistant/components/lcn/switch.py
+++ b/homeassistant/components/lcn/switch.py
@@ -5,8 +5,6 @@ from homeassistant.const import CONF_ADDRESS
from . import LcnDevice, get_connection
from .const import CONF_CONNECTIONS, CONF_OUTPUT, DATA_LCN, OUTPUT_PORTS
-DEPENDENCIES = ['lcn']
-
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/lg_netcast/manifest.json b/homeassistant/components/lg_netcast/manifest.json
new file mode 100644
index 00000000000..1728aa50614
--- /dev/null
+++ b/homeassistant/components/lg_netcast/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lg_netcast",
+ "name": "Lg netcast",
+ "documentation": "https://www.home-assistant.io/components/lg_netcast",
+ "requirements": [
+ "pylgnetcast-homeassistant==0.2.0.dev0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lg_netcast/media_player.py b/homeassistant/components/lg_netcast/media_player.py
index 7c5d9789372..da5946de1ef 100644
--- a/homeassistant/components/lg_netcast/media_player.py
+++ b/homeassistant/components/lg_netcast/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for LG TV running on NetCast 3 or 4.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.lg_netcast/
-"""
+"""Support for LG TV running on NetCast 3 or 4."""
from datetime import timedelta
import logging
@@ -22,8 +17,6 @@ from homeassistant.const import (
STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pylgnetcast-homeassistant==0.2.0.dev0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'LG TV Remote'
diff --git a/homeassistant/components/lg_soundbar/manifest.json b/homeassistant/components/lg_soundbar/manifest.json
new file mode 100644
index 00000000000..b09c8809382
--- /dev/null
+++ b/homeassistant/components/lg_soundbar/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lg_soundbar",
+ "name": "Lg soundbar",
+ "documentation": "https://www.home-assistant.io/components/lg_soundbar",
+ "requirements": [
+ "temescal==0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py
index b45baf88bca..938b4e437c1 100644
--- a/homeassistant/components/lg_soundbar/media_player.py
+++ b/homeassistant/components/lg_soundbar/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for LG soundbars.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.lg_soundbar/
-"""
+"""Support for LG soundbars."""
import logging
from homeassistant.components.media_player import (
@@ -14,8 +9,6 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import STATE_ON
-REQUIREMENTS = ['temescal==0.1']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_LG = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | SUPPORT_SELECT_SOURCE \
diff --git a/homeassistant/components/lifx/.translations/ko.json b/homeassistant/components/lifx/.translations/ko.json
index c795c54badb..2f3ec6db13d 100644
--- a/homeassistant/components/lifx/.translations/ko.json
+++ b/homeassistant/components/lifx/.translations/ko.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "no_devices_found": "LIFX \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
+ "no_devices_found": "LIFX \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "\ud558\ub098\uc758 LIFX \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"step": {
diff --git a/homeassistant/components/lifx/__init__.py b/homeassistant/components/lifx/__init__.py
index 82802bab4af..849fecad487 100644
--- a/homeassistant/components/lifx/__init__.py
+++ b/homeassistant/components/lifx/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.helpers import config_entry_flow
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
DOMAIN = 'lifx'
-REQUIREMENTS = ['aiolifx==0.6.7']
-
CONF_SERVER = 'server'
CONF_BROADCAST = 'broadcast'
diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py
index 014ca9ae6c8..04f756e6ded 100644
--- a/homeassistant/components/lifx/light.py
+++ b/homeassistant/components/lifx/light.py
@@ -30,9 +30,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lifx']
-REQUIREMENTS = ['aiolifx_effects==0.2.1']
-
SCAN_INTERVAL = timedelta(seconds=10)
DISCOVERY_INTERVAL = 60
diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json
new file mode 100644
index 00000000000..6b811b01f51
--- /dev/null
+++ b/homeassistant/components/lifx/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "lifx",
+ "name": "Lifx",
+ "documentation": "https://www.home-assistant.io/components/lifx",
+ "requirements": [
+ "aiolifx==0.6.7",
+ "aiolifx_effects==0.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@amelchio"
+ ]
+}
diff --git a/homeassistant/components/lifx/services.yaml b/homeassistant/components/lifx/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/lifx_cloud/manifest.json b/homeassistant/components/lifx_cloud/manifest.json
new file mode 100644
index 00000000000..c2834fbc788
--- /dev/null
+++ b/homeassistant/components/lifx_cloud/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lifx_cloud",
+ "name": "Lifx cloud",
+ "documentation": "https://www.home-assistant.io/components/lifx_cloud",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@amelchio"
+ ]
+}
diff --git a/homeassistant/components/lifx_legacy/light.py b/homeassistant/components/lifx_legacy/light.py
index 6c5f68937f8..a31b875f21e 100644
--- a/homeassistant/components/lifx_legacy/light.py
+++ b/homeassistant/components/lifx_legacy/light.py
@@ -22,8 +22,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['liffylights==0.9.4']
-
BYTE_MAX = 255
CONF_BROADCAST = 'broadcast'
diff --git a/homeassistant/components/lifx_legacy/manifest.json b/homeassistant/components/lifx_legacy/manifest.json
new file mode 100644
index 00000000000..4ff59ac1770
--- /dev/null
+++ b/homeassistant/components/lifx_legacy/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "lifx_legacy",
+ "name": "Lifx legacy",
+ "documentation": "https://www.home-assistant.io/components/lifx_legacy",
+ "requirements": [
+ "liffylights==0.9.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@amelchio"
+ ]
+}
diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py
index acf95a3c081..f9ce6eb05d4 100644
--- a/homeassistant/components/light/__init__.py
+++ b/homeassistant/components/light/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provides functionality to interact with lights.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/light/
-"""
+"""Provides functionality to interact with lights."""
import asyncio
import csv
from datetime import timedelta
@@ -29,7 +24,6 @@ from homeassistant.loader import bind_hass
import homeassistant.util.color as color_util
DOMAIN = 'light'
-DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=30)
GROUP_NAME_ALL_LIGHTS = 'all lights'
@@ -175,6 +169,17 @@ def preprocess_turn_on_alternatives(params):
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
+def preprocess_turn_off(params):
+ """Process data for turning light off if brightness is 0."""
+ if ATTR_BRIGHTNESS in params and params[ATTR_BRIGHTNESS] == 0:
+ # Zero brightness: Light will be turned off
+ params = {k: v for k, v in params.items() if k in [ATTR_TRANSITION,
+ ATTR_FLASH]}
+ return (True, params) # Light should be turned off
+
+ return (False, None) # Light should be turned on
+
+
class SetIntentHandler(intent.IntentHandler):
"""Handle set color intents."""
@@ -272,17 +277,24 @@ async def async_setup(hass, config):
)
preprocess_turn_on_alternatives(params)
+ turn_lights_off, off_params = preprocess_turn_off(params)
update_tasks = []
for light in target_lights:
light.async_set_context(service.context)
pars = params
+ off_pars = off_params
+ turn_light_off = turn_lights_off
if not pars:
pars = params.copy()
pars[ATTR_PROFILE] = Profiles.get_default(light.entity_id)
preprocess_turn_on_alternatives(pars)
- await light.async_turn_on(**pars)
+ turn_light_off, off_pars = preprocess_turn_off(pars)
+ if turn_light_off:
+ await light.async_turn_off(**off_pars)
+ else:
+ await light.async_turn_on(**pars)
if not light.should_poll:
continue
@@ -447,7 +459,7 @@ class Light(ToggleEntity):
if supported_features & SUPPORT_COLOR_TEMP:
data[ATTR_COLOR_TEMP] = self.color_temp
- if self.supported_features & SUPPORT_COLOR and self.hs_color:
+ if supported_features & SUPPORT_COLOR and self.hs_color:
# pylint: disable=unsubscriptable-object,not-an-iterable
hs_color = self.hs_color
data[ATTR_HS_COLOR] = (
diff --git a/homeassistant/components/light/manifest.json b/homeassistant/components/light/manifest.json
new file mode 100644
index 00000000000..62eb96967f5
--- /dev/null
+++ b/homeassistant/components/light/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "light",
+ "name": "Light",
+ "documentation": "https://www.home-assistant.io/components/light",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml
index cdf82e97429..ef944d75efc 100644
--- a/homeassistant/components/light/services.yaml
+++ b/homeassistant/components/light/services.yaml
@@ -31,10 +31,10 @@ turn_on:
description: Number between 0..255 indicating level of white.
example: '250'
brightness:
- description: Number between 0..255 indicating brightness.
+ description: Number between 0..255 indicating brightness, where 0 turns the light off, 1 is the minimum brightness and 255 is the maximum brightness supported by the light.
example: 120
brightness_pct:
- description: Number between 0..100 indicating percentage of full brightness.
+ description: Number between 0..100 indicating percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness and 100 is the maximum brightness supported by the light.
example: 47
profile:
description: Name of a light profile to use.
@@ -71,16 +71,6 @@ toggle:
'...':
description: All turn_on parameters can be used.
-hue_activate_scene:
- description: Activate a hue scene stored in the hue hub.
- fields:
- group_name:
- description: Name of hue group/room from the hue app.
- example: "Living Room"
- scene_name:
- description: Name of hue scene from the hue app.
- example: "Energize"
-
lifx_set_state:
description: Set a color/brightness and possibliy turn the light on/off.
fields:
diff --git a/homeassistant/components/lightwave/__init__.py b/homeassistant/components/lightwave/__init__.py
index a9e5dcf9823..2337c582b2d 100644
--- a/homeassistant/components/lightwave/__init__.py
+++ b/homeassistant/components/lightwave/__init__.py
@@ -5,8 +5,6 @@ from homeassistant.const import (CONF_HOST, CONF_LIGHTS, CONF_NAME,
CONF_SWITCHES)
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ['lightwave==0.15']
-
LIGHTWAVE_LINK = 'lightwave_link'
DOMAIN = 'lightwave'
@@ -14,7 +12,7 @@ DOMAIN = 'lightwave'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema(
- cv.has_at_least_one_key(CONF_LIGHTS, CONF_SWITCHES), {
+ vol.All(cv.has_at_least_one_key(CONF_LIGHTS, CONF_SWITCHES), {
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_LIGHTS, default={}): {
cv.string: vol.Schema({vol.Required(CONF_NAME): cv.string}),
@@ -23,6 +21,7 @@ CONFIG_SCHEMA = vol.Schema({
cv.string: vol.Schema({vol.Required(CONF_NAME): cv.string}),
}
})
+ )
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/lightwave/light.py b/homeassistant/components/lightwave/light.py
index f22533d2548..68c94300317 100644
--- a/homeassistant/components/lightwave/light.py
+++ b/homeassistant/components/lightwave/light.py
@@ -5,8 +5,6 @@ from homeassistant.const import CONF_NAME
from . import LIGHTWAVE_LINK
-DEPENDENCIES = ['lightwave']
-
MAX_BRIGHTNESS = 255
diff --git a/homeassistant/components/lightwave/manifest.json b/homeassistant/components/lightwave/manifest.json
new file mode 100644
index 00000000000..a26500f69a6
--- /dev/null
+++ b/homeassistant/components/lightwave/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lightwave",
+ "name": "Lightwave",
+ "documentation": "https://www.home-assistant.io/components/lightwave",
+ "requirements": [
+ "lightwave==0.15"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lightwave/switch.py b/homeassistant/components/lightwave/switch.py
index dfa93b4b151..0d7e2cd3825 100644
--- a/homeassistant/components/lightwave/switch.py
+++ b/homeassistant/components/lightwave/switch.py
@@ -4,8 +4,6 @@ from homeassistant.const import CONF_NAME
from . import LIGHTWAVE_LINK
-DEPENDENCIES = ['lightwave']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/limitlessled/light.py b/homeassistant/components/limitlessled/light.py
index 3a0225d8d65..fa12bc76de5 100644
--- a/homeassistant/components/limitlessled/light.py
+++ b/homeassistant/components/limitlessled/light.py
@@ -1,9 +1,4 @@
-"""
-Support for LimitlessLED bulbs.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.limitlessled/
-"""
+"""Support for LimitlessLED bulbs."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.util.color import (
color_temperature_mired_to_kelvin, color_hs_to_RGB)
from homeassistant.helpers.restore_state import RestoreEntity
-REQUIREMENTS = ['limitlessled==1.1.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_BRIDGES = 'bridges'
diff --git a/homeassistant/components/limitlessled/manifest.json b/homeassistant/components/limitlessled/manifest.json
new file mode 100644
index 00000000000..f8b42fabcbe
--- /dev/null
+++ b/homeassistant/components/limitlessled/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "limitlessled",
+ "name": "Limitlessled",
+ "documentation": "https://www.home-assistant.io/components/limitlessled",
+ "requirements": [
+ "limitlessled==1.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/linksys_ap/device_tracker.py b/homeassistant/components/linksys_ap/device_tracker.py
index 5638db4caaf..3871d5beda9 100644
--- a/homeassistant/components/linksys_ap/device_tracker.py
+++ b/homeassistant/components/linksys_ap/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Linksys Access Points.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.linksys_ap/
-"""
+"""Support for Linksys Access Points."""
import base64
import logging
@@ -19,8 +14,6 @@ from homeassistant.const import (
INTERFACES = 2
DEFAULT_TIMEOUT = 10
-REQUIREMENTS = ['beautifulsoup4==4.7.1']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/linksys_ap/manifest.json b/homeassistant/components/linksys_ap/manifest.json
new file mode 100644
index 00000000000..ccad7298d6b
--- /dev/null
+++ b/homeassistant/components/linksys_ap/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "linksys_ap",
+ "name": "Linksys ap",
+ "documentation": "https://www.home-assistant.io/components/linksys_ap",
+ "requirements": [
+ "beautifulsoup4==4.7.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/linksys_smart/manifest.json b/homeassistant/components/linksys_smart/manifest.json
new file mode 100644
index 00000000000..19bb079c29c
--- /dev/null
+++ b/homeassistant/components/linksys_smart/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "linksys_smart",
+ "name": "Linksys smart",
+ "documentation": "https://www.home-assistant.io/components/linksys_smart",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/linky/manifest.json b/homeassistant/components/linky/manifest.json
new file mode 100644
index 00000000000..706962b5c4d
--- /dev/null
+++ b/homeassistant/components/linky/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "linky",
+ "name": "Linky",
+ "documentation": "https://www.home-assistant.io/components/linky",
+ "requirements": [
+ "pylinky==0.3.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/linky/sensor.py b/homeassistant/components/linky/sensor.py
index 46e7ed92f45..63f7aaf5423 100644
--- a/homeassistant/components/linky/sensor.py
+++ b/homeassistant/components/linky/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Linky.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.linky/
-"""
+"""Support for Linky."""
import logging
import json
from datetime import timedelta
@@ -17,7 +12,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pylinky==0.3.0']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=10)
diff --git a/homeassistant/components/linode/__init__.py b/homeassistant/components/linode/__init__.py
index 8bbd98c0acf..f9270d95c07 100644
--- a/homeassistant/components/linode/__init__.py
+++ b/homeassistant/components/linode/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_ACCESS_TOKEN
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['linode-api==4.1.9b1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CREATED = 'created'
diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py
index 19455917dbb..69079b3e63a 100644
--- a/homeassistant/components/linode/binary_sensor.py
+++ b/homeassistant/components/linode/binary_sensor.py
@@ -16,8 +16,6 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Node'
DEFAULT_DEVICE_CLASS = 'moving'
-DEPENDENCIES = ['linode']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NODES): vol.All(cv.ensure_list, [cv.string]),
})
diff --git a/homeassistant/components/linode/manifest.json b/homeassistant/components/linode/manifest.json
new file mode 100644
index 00000000000..7dc2e0d7518
--- /dev/null
+++ b/homeassistant/components/linode/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "linode",
+ "name": "Linode",
+ "documentation": "https://www.home-assistant.io/components/linode",
+ "requirements": [
+ "linode-api==4.1.9b1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py
index e5f97ef756e..6787d84937f 100644
--- a/homeassistant/components/linode/switch.py
+++ b/homeassistant/components/linode/switch.py
@@ -13,8 +13,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['linode']
-
DEFAULT_NAME = 'Node'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/linux_battery/manifest.json b/homeassistant/components/linux_battery/manifest.json
new file mode 100644
index 00000000000..4c32b88b2d5
--- /dev/null
+++ b/homeassistant/components/linux_battery/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "linux_battery",
+ "name": "Linux battery",
+ "documentation": "https://www.home-assistant.io/components/linux_battery",
+ "requirements": [
+ "batinfo==0.4.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/linux_battery/sensor.py b/homeassistant/components/linux_battery/sensor.py
index 69fc145a4a5..87061174d2d 100644
--- a/homeassistant/components/linux_battery/sensor.py
+++ b/homeassistant/components/linux_battery/sensor.py
@@ -1,9 +1,4 @@
-"""
-Details about the built-in battery.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.linux_battery/
-"""
+"""Details about the built-in battery."""
import logging
import os
@@ -14,8 +9,6 @@ from homeassistant.const import ATTR_NAME, CONF_NAME, DEVICE_CLASS_BATTERY
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['batinfo==0.4.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_PATH = 'path'
diff --git a/homeassistant/components/lirc/__init__.py b/homeassistant/components/lirc/__init__.py
index 0f00eda2007..c3077cf6f44 100644
--- a/homeassistant/components/lirc/__init__.py
+++ b/homeassistant/components/lirc/__init__.py
@@ -9,8 +9,6 @@ import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
-REQUIREMENTS = ['python-lirc==1.2.3']
-
_LOGGER = logging.getLogger(__name__)
BUTTON_NAME = 'button_name'
diff --git a/homeassistant/components/lirc/manifest.json b/homeassistant/components/lirc/manifest.json
new file mode 100644
index 00000000000..d11cf0b2f1e
--- /dev/null
+++ b/homeassistant/components/lirc/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lirc",
+ "name": "Lirc",
+ "documentation": "https://www.home-assistant.io/components/lirc",
+ "requirements": [
+ "python-lirc==1.2.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py
index b4e8e45fa0b..d8e02b51870 100644
--- a/homeassistant/components/litejet/__init__.py
+++ b/homeassistant/components/litejet/__init__.py
@@ -7,8 +7,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.const import CONF_PORT
-REQUIREMENTS = ['pylitejet==0.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_NAMES = 'exclude_names'
diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py
index 8662afc668e..b87d77ebe7c 100644
--- a/homeassistant/components/litejet/light.py
+++ b/homeassistant/components/litejet/light.py
@@ -1,9 +1,4 @@
-"""
-Support for LiteJet lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.litejet/
-"""
+"""Support for LiteJet lights."""
import logging
from homeassistant.components import litejet
@@ -12,8 +7,6 @@ from homeassistant.components.light import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['litejet']
-
ATTR_NUMBER = 'number'
diff --git a/homeassistant/components/litejet/manifest.json b/homeassistant/components/litejet/manifest.json
new file mode 100644
index 00000000000..08bcac67903
--- /dev/null
+++ b/homeassistant/components/litejet/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "litejet",
+ "name": "Litejet",
+ "documentation": "https://www.home-assistant.io/components/litejet",
+ "requirements": [
+ "pylitejet==0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py
index 2563c9ceb0c..c347140a6bd 100644
--- a/homeassistant/components/litejet/scene.py
+++ b/homeassistant/components/litejet/scene.py
@@ -4,8 +4,6 @@ import logging
from homeassistant.components import litejet
from homeassistant.components.scene import Scene
-DEPENDENCIES = ['litejet']
-
ATTR_NUMBER = 'number'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py
index b9755569fd2..7e3059dacd6 100644
--- a/homeassistant/components/litejet/switch.py
+++ b/homeassistant/components/litejet/switch.py
@@ -1,16 +1,9 @@
-"""
-Support for LiteJet switch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.litejet/
-"""
+"""Support for LiteJet switch."""
import logging
from homeassistant.components import litejet
from homeassistant.components.switch import SwitchDevice
-DEPENDENCIES = ['litejet']
-
ATTR_NUMBER = 'number'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/liveboxplaytv/manifest.json b/homeassistant/components/liveboxplaytv/manifest.json
new file mode 100644
index 00000000000..863507ada6c
--- /dev/null
+++ b/homeassistant/components/liveboxplaytv/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "liveboxplaytv",
+ "name": "Liveboxplaytv",
+ "documentation": "https://www.home-assistant.io/components/liveboxplaytv",
+ "requirements": [
+ "liveboxplaytv==2.0.2",
+ "pyteleloisirs==3.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@pschmitt"
+ ]
+}
diff --git a/homeassistant/components/liveboxplaytv/media_player.py b/homeassistant/components/liveboxplaytv/media_player.py
index f69c3c67aec..05ceb68cc94 100644
--- a/homeassistant/components/liveboxplaytv/media_player.py
+++ b/homeassistant/components/liveboxplaytv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with an Orange Livebox Play TV appliance.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.liveboxplaytv/
-"""
+"""Support for interface with an Orange Livebox Play TV appliance."""
from datetime import timedelta
import logging
@@ -23,8 +18,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['liveboxplaytv==2.0.2', 'pyteleloisirs==3.4']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Livebox Play TV'
diff --git a/homeassistant/components/llamalab_automate/manifest.json b/homeassistant/components/llamalab_automate/manifest.json
new file mode 100644
index 00000000000..e66050fceb5
--- /dev/null
+++ b/homeassistant/components/llamalab_automate/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "llamalab_automate",
+ "name": "Llamalab automate",
+ "documentation": "https://www.home-assistant.io/components/llamalab_automate",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/llamalab_automate/notify.py b/homeassistant/components/llamalab_automate/notify.py
index 6b59d11e0a3..d43988ada43 100644
--- a/homeassistant/components/llamalab_automate/notify.py
+++ b/homeassistant/components/llamalab_automate/notify.py
@@ -1,9 +1,4 @@
-"""
-LlamaLab Automate notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.llamalab_automate/
-"""
+"""LlamaLab Automate notification service."""
import logging
import requests
diff --git a/homeassistant/components/local_file/camera.py b/homeassistant/components/local_file/camera.py
index 56780d16f56..444f4109e98 100644
--- a/homeassistant/components/local_file/camera.py
+++ b/homeassistant/components/local_file/camera.py
@@ -1,9 +1,4 @@
-"""
-Camera that loads a picture from a local file.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.local_file/
-"""
+"""Camera that loads a picture from a local file."""
import logging
import mimetypes
import os
diff --git a/homeassistant/components/local_file/manifest.json b/homeassistant/components/local_file/manifest.json
new file mode 100644
index 00000000000..14a503f33f5
--- /dev/null
+++ b/homeassistant/components/local_file/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "local_file",
+ "name": "Local file",
+ "documentation": "https://www.home-assistant.io/components/local_file",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/local_file/services.yaml b/homeassistant/components/local_file/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/locative/.translations/es.json b/homeassistant/components/locative/.translations/es.json
new file mode 100644
index 00000000000..e48d33ba52d
--- /dev/null
+++ b/homeassistant/components/locative/.translations/es.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de Geofency.",
+ "one_instance_allowed": "Solo se necesita una instancia."
+ },
+ "create_entry": {
+ "default": "Para enviar ubicaciones a Home Assistant, es necesario configurar la caracter\u00edstica webhook en la app de Locative.\n\nRellena la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n\nRevisa [la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles."
+ },
+ "step": {
+ "user": {
+ "description": "\u00bfEst\u00e1s seguro que quieres configurar el webhook de Locative?",
+ "title": "Configurar el webhook de Locative"
+ }
+ },
+ "title": "Webhook de Locative"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py
index e6a5b56ecda..f21c55af28a 100644
--- a/homeassistant/components/locative/__init__.py
+++ b/homeassistant/components/locative/__init__.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'locative'
-DEPENDENCIES = ['webhook']
-
TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN)
@@ -141,10 +139,14 @@ async def async_setup_entry(hass, entry):
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
-
await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER)
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'Locative Webhook',
diff --git a/homeassistant/components/locative/device_tracker.py b/homeassistant/components/locative/device_tracker.py
index 9dbf7e74fe3..1e16bde58ad 100644
--- a/homeassistant/components/locative/device_tracker.py
+++ b/homeassistant/components/locative/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for the Locative platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.locative/
-"""
+"""Support for the Locative platform."""
import logging
from homeassistant.components.device_tracker import (
@@ -15,8 +10,6 @@ from . import DOMAIN as LOCATIVE_DOMAIN, TRACKER_UPDATE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['locative']
-
DATA_KEY = '{}.{}'.format(LOCATIVE_DOMAIN, DEVICE_TRACKER_DOMAIN)
diff --git a/homeassistant/components/locative/manifest.json b/homeassistant/components/locative/manifest.json
new file mode 100644
index 00000000000..afe2850caf8
--- /dev/null
+++ b/homeassistant/components/locative/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "locative",
+ "name": "Locative",
+ "documentation": "https://www.home-assistant.io/components/locative",
+ "requirements": [],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py
index 71c838679fb..598de7961a5 100644
--- a/homeassistant/components/lock/__init__.py
+++ b/homeassistant/components/lock/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to interface with various locks that can be controlled remotely.
-
-For more details about this component, please refer to the documentation
-at https://home-assistant.io/components/lock/
-"""
+"""Component to interface with locks that can be controlled remotely."""
from datetime import timedelta
import functools as ft
import logging
@@ -24,7 +19,6 @@ from homeassistant.components import group
ATTR_CHANGED_BY = 'changed_by'
DOMAIN = 'lock'
-DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
diff --git a/homeassistant/components/lock/manifest.json b/homeassistant/components/lock/manifest.json
new file mode 100644
index 00000000000..29a7a5513d0
--- /dev/null
+++ b/homeassistant/components/lock/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lock",
+ "name": "Lock",
+ "documentation": "https://www.home-assistant.io/components/lock",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lockitron/lock.py b/homeassistant/components/lockitron/lock.py
index b190a5cd2cd..0ec838f4d4b 100644
--- a/homeassistant/components/lockitron/lock.py
+++ b/homeassistant/components/lockitron/lock.py
@@ -1,9 +1,4 @@
-"""
-Lockitron lock platform.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/lockitron/
-"""
+"""Lockitron lock platform."""
import logging
import requests
diff --git a/homeassistant/components/lockitron/manifest.json b/homeassistant/components/lockitron/manifest.json
new file mode 100644
index 00000000000..b515d65a14f
--- /dev/null
+++ b/homeassistant/components/lockitron/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "lockitron",
+ "name": "Lockitron",
+ "documentation": "https://www.home-assistant.io/components/lockitron",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py
index 7a0fb5e2654..70fe31e64d6 100644
--- a/homeassistant/components/logbook/__init__.py
+++ b/homeassistant/components/logbook/__init__.py
@@ -31,8 +31,6 @@ CONF_DOMAINS = 'domains'
CONF_ENTITIES = 'entities'
CONTINUOUS_DOMAINS = ['proximity', 'sensor']
-DEPENDENCIES = ['recorder', 'frontend']
-
DOMAIN = 'logbook'
GROUP_BY_MINUTES = 15
diff --git a/homeassistant/components/logbook/manifest.json b/homeassistant/components/logbook/manifest.json
new file mode 100644
index 00000000000..cedce8152a2
--- /dev/null
+++ b/homeassistant/components/logbook/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "logbook",
+ "name": "Logbook",
+ "documentation": "https://www.home-assistant.io/components/logbook",
+ "requirements": [],
+ "dependencies": [
+ "frontend",
+ "recorder"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/logbook/services.yaml b/homeassistant/components/logbook/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/logentries/manifest.json b/homeassistant/components/logentries/manifest.json
new file mode 100644
index 00000000000..60be8f275ee
--- /dev/null
+++ b/homeassistant/components/logentries/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "logentries",
+ "name": "Logentries",
+ "documentation": "https://www.home-assistant.io/components/logentries",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/logger/manifest.json b/homeassistant/components/logger/manifest.json
new file mode 100644
index 00000000000..c6b62387039
--- /dev/null
+++ b/homeassistant/components/logger/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "logger",
+ "name": "Logger",
+ "documentation": "https://www.home-assistant.io/components/logger",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/logi_circle/.translations/ca.json b/homeassistant/components/logi_circle/.translations/ca.json
new file mode 100644
index 00000000000..f3c201d19fc
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/ca.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Nom\u00e9s pots configurar un \u00fanic compte de Logi Circule.",
+ "external_error": "S'ha produ\u00eft una excepci\u00f3 d\u2019un altre flux de dades.",
+ "external_setup": "Logi Circle s'ha configurat correctament des d'un altre flux de dades.",
+ "no_flows": "Necessites configurar Logi Circle abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "Autenticaci\u00f3 exitosa amb Logi Circle."
+ },
+ "error": {
+ "auth_error": "Ha fallat l\u2019autoritzaci\u00f3 de l\u2019API.",
+ "auth_timeout": "L\u2019autoritzaci\u00f3 ha expirat durant l'obtenci\u00f3 del testimoni d\u2019acc\u00e9s.",
+ "follow_link": "V\u00e9s a l'enlla\u00e7 i autentica't abans de pr\u00e9mer Enviar"
+ },
+ "step": {
+ "auth": {
+ "description": "V\u00e9s a l'enlla\u00e7 de sota i Accepta l'acc\u00e9s al teu compte de Logi Circle, despr\u00e9s, torna i prem Enviar (tamb\u00e9 a sota).\n\n[Enlla\u00e7]({authorization_url})",
+ "title": "Autenticaci\u00f3 amb Logi Circle"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "Prove\u00efdor"
+ },
+ "description": "Tria quin prove\u00efdor d'autenticaci\u00f3 vols utilitzar per autenticar-te amb Logi Circle.",
+ "title": "Prove\u00efdor d'autenticaci\u00f3"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/de.json b/homeassistant/components/logi_circle/.translations/de.json
new file mode 100644
index 00000000000..4d7ef918ddc
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/de.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Es kann nur ein einziges Logi Circle-Konto konfiguriert werden.",
+ "external_error": "Es ist eine Ausnahme in einem anderen Flow aufgetreten.",
+ "external_setup": "Logi Circle wurde erfolgreich aus einem anderen Flow konfiguriert.",
+ "no_flows": "Logi Circle muss konfiguriert werden, bevor die Authentifizierung erfolgen kann. [Bitte lies die Anweisungen] (https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "Erfolgreiche Authentifizierung mit Logi Circle."
+ },
+ "error": {
+ "auth_error": "Die API-Autorisierung ist fehlgeschlagen.",
+ "auth_timeout": "Zeit\u00fcberschreitung der Autorisierung beim Anfordern des Zugriffstokens.",
+ "follow_link": "Bitte folge dem Link und authentifiziere dich, bevor du auf Senden klickst."
+ },
+ "step": {
+ "auth": {
+ "description": "Folge dem Link unten und klicke Akzeptieren um auf dein Logi Circle-Konto zuzugreifen. Kehre dann zur\u00fcck und dr\u00fccke unten auf Senden . \n\n [Link] ({authorization_url})",
+ "title": "Authentifizierung mit Logi Circle"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "Anbieter"
+ },
+ "description": "W\u00e4hle aus, \u00fcber welchen Anbieter du dich bei Logi Circle authentifizieren m\u00f6chtest.",
+ "title": "Authentifizierungsanbieter"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/en.json b/homeassistant/components/logi_circle/.translations/en.json
new file mode 100644
index 00000000000..bf3c059f81a
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/en.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "You can only configure a single Logi Circle account.",
+ "external_error": "Exception occurred from another flow.",
+ "external_setup": "Logi Circle successfully configured from another flow.",
+ "no_flows": "You need to configure Logi Circle before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "Successfully authenticated with Logi Circle."
+ },
+ "error": {
+ "auth_error": "API authorization failed.",
+ "auth_timeout": "Authorization timed out when requesting access token.",
+ "follow_link": "Please follow the link and authenticate before pressing Submit."
+ },
+ "step": {
+ "auth": {
+ "description": "Please follow the link below and Accept access to your Logi Circle account, then come back and press Submit below.\n\n[Link]({authorization_url})",
+ "title": "Authenticate with Logi Circle"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "Provider"
+ },
+ "description": "Pick via which authentication provider you want to authenticate with Logi Circle.",
+ "title": "Authentication Provider"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/es.json b/homeassistant/components/logi_circle/.translations/es.json
new file mode 100644
index 00000000000..4819ff5cdd7
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/es.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Solo puedes configurar una cuenta de Logi Circle.",
+ "external_error": "La excepci\u00f3n se produjo a partir de otro flujo.",
+ "external_setup": "Logi Circle se ha configurado correctamente a partir de otro flujo.",
+ "no_flows": "Es necesario configurar Logi Circle antes de poder autenticarse con \u00e9l. [Echa un vistazo a las instrucciones] (https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "Autenticado correctamente con Logi Circle."
+ },
+ "error": {
+ "auth_error": "Error en la autorizaci\u00f3n de la API.",
+ "auth_timeout": "Se ha agotado el tiempo de espera de la autorizaci\u00f3n al solicitar el token de acceso.",
+ "follow_link": "Accede al enlace e identif\u00edcate antes de pulsar Enviar."
+ },
+ "step": {
+ "auth": {
+ "description": "Accede al siguiente enlace y Acepta el acceso a tu cuenta Logi Circle, despu\u00e9s vuelve y pulsa en Enviar a continuaci\u00f3n.\n\n[Link]({authorization_url})",
+ "title": "Autenticaci\u00f3n con Logi Circle"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "Proveedor"
+ },
+ "description": "Elige a trav\u00e9s de qu\u00e9 proveedor de autenticaci\u00f3n quieres autenticarte con Logi Circle.",
+ "title": "Proveedor de autenticaci\u00f3n"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/ko.json b/homeassistant/components/logi_circle/.translations/ko.json
new file mode 100644
index 00000000000..577f3475b58
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/ko.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "\ud558\ub098\uc758 Logi Circle \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.",
+ "external_error": "\ub2e4\ub978 Flow \uc5d0\uc11c \uc608\uc678\uc0ac\ud56d\uc774 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
+ "external_setup": "Logi Circle \uc774 \ub2e4\ub978 Flow \uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
+ "no_flows": "Logi Circle \uc744 \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Logi Circle \uc744 \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/logi_circle/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694."
+ },
+ "create_entry": {
+ "default": "Logi Circle \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4."
+ },
+ "error": {
+ "auth_error": "API \uc2b9\uc778\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.",
+ "auth_timeout": "\uc5d1\uc138\uc2a4 \ud1a0\ud070 \uc694\uccad\uc911 \uc2b9\uc778 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
+ "follow_link": "Submit \ubc84\ud2bc\uc744 \ub204\ub974\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694"
+ },
+ "step": {
+ "auth": {
+ "description": "\uc544\ub798 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud558\uc5ec Logi Circle \uacc4\uc815\uc5d0 \ub300\ud574 \ub3d9\uc758 \ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 Submit \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.\n\n[\ub9c1\ud06c]({authorization_url})",
+ "title": "Logi Circle \uc778\uc99d"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "\uacf5\uae09\uc790"
+ },
+ "description": "Logi Circle \uc744 \uc778\uc99d\ud558\uae30 \uc704\ud55c \uc778\uc99d \uacf5\uae09\uc790\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.",
+ "title": "\uc778\uc99d \uacf5\uae09\uc790"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/lb.json b/homeassistant/components/logi_circle/.translations/lb.json
new file mode 100644
index 00000000000..b0befa80fd4
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/lb.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Logi Circle Kont konfigur\u00e9ieren.",
+ "external_error": "Ausnam vun engem anere Floss.",
+ "external_setup": "Logi Circle gouf vun engem anere Floss erfollegr\u00e4ich konfigur\u00e9iert.",
+ "no_flows": "Dir musst Logi Circle konfigur\u00e9ieren, ier Dir d\u00ebs Authentifiz\u00e9ierung k\u00ebnnt benotzen.[Liest w.e.g. d'Instruktioune](https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "Erfollegr\u00e4ich mat Logi Circle authentifiz\u00e9iert."
+ },
+ "error": {
+ "auth_error": "Feeler bei der API Autorisatioun.",
+ "auth_timeout": "Z\u00e4it Iwwerschreidung vun der Autorisatioun beim ufroe vum Acc\u00e8s Jeton.",
+ "follow_link": "Follegt w.e.g dem Link an authentifiz\u00e9iert iech ier de op Ofsch\u00e9cken dr\u00e9ckt."
+ },
+ "step": {
+ "auth": {
+ "description": "Follegt dem Link \u00ebnnendr\u00ebnner an accept\u00e9iert den Acc\u00e8s zu \u00e4rem Logi Circle Kont , a kommt dann zer\u00e9ck heihin an dr\u00e9ck op ofsch\u00e9cken hei \u00ebnnen.\n\n[Link]({authorization_url})",
+ "title": "Mat Logi Circle authentifiz\u00e9ieren"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "Ubidder"
+ },
+ "description": "Wielt den Authentifikatioun Ubidder deen sech mat Logi Circle verbanne soll.",
+ "title": "Authentifikatioun Ubidder"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/nl.json b/homeassistant/components/logi_circle/.translations/nl.json
new file mode 100644
index 00000000000..fe1708568bd
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/nl.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/nn.json b/homeassistant/components/logi_circle/.translations/nn.json
new file mode 100644
index 00000000000..0ea648256d3
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/nn.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "create_entry": {
+ "default": "Vellukka autentisering med Logi Circle"
+ },
+ "error": {
+ "auth_error": "API-autorisasjonen mislyktes."
+ },
+ "step": {
+ "auth": {
+ "title": "Godkjenn med Logi Circle"
+ },
+ "user": {
+ "description": "Vel kva for ein autentiseringsleverand\u00f8r du vil godkjenne med Logi Circle"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/no.json b/homeassistant/components/logi_circle/.translations/no.json
new file mode 100644
index 00000000000..03c128f636c
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/no.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "Du kan bare konfigurere en enkelt Logi Circle konto.",
+ "external_error": "Det oppstod et unntak fra en annen flow.",
+ "external_setup": "Logi Circle er vellykket konfigurert fra en annen flow.",
+ "no_flows": "Du m\u00e5 konfigurere Logi Circle f\u00f8r du kan autentisere med den. [Vennligst les instruksjonene](https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "Vellykket autentisering med Logi Circle"
+ },
+ "error": {
+ "auth_error": "API-autorisasjonen mislyktes.",
+ "auth_timeout": "Autorisasjon ble tidsavbrutt da du ba om token.",
+ "follow_link": "Vennligst f\u00f8lg lenken og godkjen f\u00f8r du trykker send."
+ },
+ "step": {
+ "auth": {
+ "description": "Vennligst f\u00f8lg lenken nedenfor og Godta tilgang til Logi Circle kontoen din, kom deretter tilbake og trykk Send nedenfor. \n\n [Link]({authorization_url})",
+ "title": "Godkjenn med Logi Circle"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "Tilbyder"
+ },
+ "description": "Velg med hvilken autentiseringsleverand\u00f8r du vil godkjenne Logi Circle.",
+ "title": "Autentiseringsleverand\u00f8r"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/ru.json b/homeassistant/components/logi_circle/.translations/ru.json
new file mode 100644
index 00000000000..5e4d0890bfd
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/ru.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
+ "external_error": "\u0418\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430.",
+ "external_setup": "Logi Circle \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430.",
+ "no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Logi Circle \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/logi_circle/)."
+ },
+ "create_entry": {
+ "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e."
+ },
+ "error": {
+ "auth_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 API.",
+ "auth_timeout": "\u041f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
+ "follow_link": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0438 \u043f\u0440\u043e\u0439\u0434\u0438\u0442\u0435 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u044c \"\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c\"."
+ },
+ "step": {
+ "auth": {
+ "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Logi Circle, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c.",
+ "title": "Logi Circle"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440"
+ },
+ "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0432\u0445\u043e\u0434.",
+ "title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/.translations/zh-Hant.json b/homeassistant/components/logi_circle/.translations/zh-Hant.json
new file mode 100644
index 00000000000..b9f82b6e2e5
--- /dev/null
+++ b/homeassistant/components/logi_circle/.translations/zh-Hant.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "abort": {
+ "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Logi Circle \u5e33\u865f\u3002",
+ "external_error": "\u5176\u4ed6\u6d41\u7a0b\u767c\u751f\u7570\u5e38\u3002",
+ "external_setup": "\u5df2\u7531\u5176\u4ed6\u6d41\u7a0b\u6210\u529f\u8a2d\u5b9a Logi Circle\u3002",
+ "no_flows": "\u5fc5\u9808\u5148\u8a2d\u5b9a Logi Circle \u65b9\u80fd\u9032\u884c\u8a8d\u8b49\u3002[\u8acb\u53c3\u95b1\u6559\u5b78\u6307\u5f15]\uff08https://www.home-assistant.io/components/logi_circle/\uff09\u3002"
+ },
+ "create_entry": {
+ "default": "\u5df2\u6210\u529f\u8a8d\u8b49 Logi Circle \u88dd\u7f6e\u3002"
+ },
+ "error": {
+ "auth_error": "API \u8a8d\u8b49\u5931\u6557\u3002",
+ "auth_timeout": "\u8acb\u6c42\u5b58\u53d6\u5bc6\u9470\u8a8d\u8b49\u903e\u6642\u3002",
+ "follow_link": "\u8acb\u65bc\u50b3\u9001\u524d\uff0c\u5148\u4f7f\u7528\u9023\u7d50\u4e26\u9032\u884c\u8a8d\u8b49\u3002"
+ },
+ "step": {
+ "auth": {
+ "description": "\u8acb\u4f7f\u7528\u4e0b\u65b9\u9023\u7d50\u4e26\u9ede\u9078\u63a5\u53d7\u4ee5\u5b58\u53d6 Logi Circle \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684\u50b3\u9001\u3002\n\n[Link]({authorization_url})",
+ "title": "\u4ee5 Logi Circle \u8a8d\u8b49"
+ },
+ "user": {
+ "data": {
+ "flow_impl": "\u63d0\u4f9b\u8005"
+ },
+ "description": "\u65bc\u8a8d\u8b49\u63d0\u4f9b\u8005\u4e2d\u6311\u9078\u6240\u8981\u9032\u884c Logi Circle \u8a8d\u8b49\u63d0\u4f9b\u8005\u3002",
+ "title": "\u8a8d\u8b49\u63d0\u4f9b\u8005"
+ }
+ },
+ "title": "Logi Circle"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py
index ef006ef8b4d..1b74a9df03b 100644
--- a/homeassistant/components/logi_circle/__init__.py
+++ b/homeassistant/components/logi_circle/__init__.py
@@ -5,71 +5,201 @@ import logging
import async_timeout
import voluptuous as vol
-from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
-import homeassistant.helpers.config_validation as cv
+from homeassistant import config_entries
+from homeassistant.components.camera import (
+ ATTR_FILENAME, CAMERA_SERVICE_SCHEMA)
+from homeassistant.const import (
+ CONF_MONITORED_CONDITIONS, CONF_SENSORS, EVENT_HOMEASSISTANT_STOP)
+from homeassistant.helpers import config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['logi_circle==0.1.7']
+from . import config_flow
+from .const import (
+ CONF_API_KEY, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_REDIRECT_URI,
+ DATA_LOGI, DEFAULT_CACHEDB, DOMAIN, LED_MODE_KEY, LOGI_SENSORS,
+ RECORDING_MODE_KEY, SIGNAL_LOGI_CIRCLE_RECONFIGURE,
+ SIGNAL_LOGI_CIRCLE_RECORD, SIGNAL_LOGI_CIRCLE_SNAPSHOT)
+
+NOTIFICATION_ID = 'logi_circle_notification'
+NOTIFICATION_TITLE = 'Logi Circle Setup'
_LOGGER = logging.getLogger(__name__)
_TIMEOUT = 15 # seconds
-ATTRIBUTION = "Data provided by circle.logi.com"
+SERVICE_SET_CONFIG = 'set_config'
+SERVICE_LIVESTREAM_SNAPSHOT = 'livestream_snapshot'
+SERVICE_LIVESTREAM_RECORD = 'livestream_record'
-NOTIFICATION_ID = 'logi_notification'
-NOTIFICATION_TITLE = 'Logi Circle Setup'
+ATTR_MODE = 'mode'
+ATTR_VALUE = 'value'
+ATTR_DURATION = 'duration'
-DOMAIN = 'logi_circle'
-DEFAULT_CACHEDB = '.logi_cache.pickle'
-DEFAULT_ENTITY_NAMESPACE = 'logi_circle'
+SENSOR_SCHEMA = vol.Schema({
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(LOGI_SENSORS)):
+ vol.All(cv.ensure_list, [vol.In(LOGI_SENSORS)])
+})
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- }),
-}, extra=vol.ALLOW_EXTRA)
+CONFIG_SCHEMA = vol.Schema(
+ {
+ DOMAIN:
+ vol.Schema({
+ vol.Required(CONF_CLIENT_ID): cv.string,
+ vol.Required(CONF_CLIENT_SECRET): cv.string,
+ vol.Required(CONF_API_KEY): cv.string,
+ vol.Required(CONF_REDIRECT_URI): cv.string,
+ vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA
+ })
+ },
+ extra=vol.ALLOW_EXTRA,
+)
+
+LOGI_CIRCLE_SERVICE_SET_CONFIG = CAMERA_SERVICE_SCHEMA.extend({
+ vol.Required(ATTR_MODE): vol.In([LED_MODE_KEY,
+ RECORDING_MODE_KEY]),
+ vol.Required(ATTR_VALUE): cv.boolean
+})
+
+LOGI_CIRCLE_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({
+ vol.Required(ATTR_FILENAME): cv.template
+})
+
+LOGI_CIRCLE_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend({
+ vol.Required(ATTR_FILENAME): cv.template,
+ vol.Required(ATTR_DURATION): cv.positive_int
+})
async def async_setup(hass, config):
- """Set up the Logi Circle component."""
+ """Set up configured Logi Circle component."""
+ if DOMAIN not in config:
+ return True
+
conf = config[DOMAIN]
- username = conf[CONF_USERNAME]
- password = conf[CONF_PASSWORD]
+
+ config_flow.register_flow_implementation(
+ hass,
+ DOMAIN,
+ client_id=conf[CONF_CLIENT_ID],
+ client_secret=conf[CONF_CLIENT_SECRET],
+ api_key=conf[CONF_API_KEY],
+ redirect_uri=conf[CONF_REDIRECT_URI],
+ sensors=conf[CONF_SENSORS])
+
+ hass.async_create_task(
+ hass.config_entries.flow.async_init(
+ DOMAIN,
+ context={'source': config_entries.SOURCE_IMPORT},
+ ))
+
+ return True
+
+
+async def async_setup_entry(hass, entry):
+ """Set up Logi Circle from a config entry."""
+ from logi_circle import LogiCircle
+ from logi_circle.exception import AuthorizationFailed
+ from aiohttp.client_exceptions import ClientResponseError
+
+ logi_circle = LogiCircle(
+ client_id=entry.data[CONF_CLIENT_ID],
+ client_secret=entry.data[CONF_CLIENT_SECRET],
+ api_key=entry.data[CONF_API_KEY],
+ redirect_uri=entry.data[CONF_REDIRECT_URI],
+ cache_file=DEFAULT_CACHEDB
+ )
+
+ if not logi_circle.authorized:
+ hass.components.persistent_notification.create(
+ "Error: The cached access tokens are missing from {}.
"
+ "Please unload then re-add the Logi Circle integration to resolve."
+ ''.format(DEFAULT_CACHEDB),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+ return False
try:
- from logi_circle import Logi
- from logi_circle.exception import BadLogin
- from aiohttp.client_exceptions import ClientResponseError
-
- cache = hass.config.path(DEFAULT_CACHEDB)
- logi = Logi(username=username, password=password, cache_file=cache)
-
with async_timeout.timeout(_TIMEOUT, loop=hass.loop):
- await logi.login()
- hass.data[DOMAIN] = await logi.cameras
-
- if not logi.is_connected:
- return False
- except (BadLogin, ClientResponseError) as ex:
- _LOGGER.error('Unable to connect to Logi Circle API: %s', str(ex))
+ # Ensure the cameras property returns the same Camera objects for
+ # all devices. Performs implicit login and session validation.
+ await logi_circle.synchronize_cameras()
+ except AuthorizationFailed:
hass.components.persistent_notification.create(
- 'Error: {}
'
- 'You will need to restart hass after fixing.'
- ''.format(ex),
+ "Error: Failed to obtain an access token from the cached "
+ "refresh token.
"
+ "Token may have expired or been revoked.
"
+ "Please unload then re-add the Logi Circle integration to resolve",
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
except asyncio.TimeoutError:
# The TimeoutError exception object returns nothing when casted to a
# string, so we'll handle it separately.
- err = '{}s timeout exceeded when connecting to Logi Circle API'.format(
+ err = "{}s timeout exceeded when connecting to Logi Circle API".format(
_TIMEOUT)
- _LOGGER.error(err)
hass.components.persistent_notification.create(
- 'Error: {}
'
- 'You will need to restart hass after fixing.'
+ "Error: {}
"
+ "You will need to restart hass after fixing."
''.format(err),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
+ except ClientResponseError as ex:
+ hass.components.persistent_notification.create(
+ "Error: {}
"
+ "You will need to restart hass after fixing."
+ ''.format(ex),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+ return False
+
+ hass.data[DATA_LOGI] = logi_circle
+
+ for component in 'camera', 'sensor':
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ entry, component))
+
+ async def service_handler(service):
+ """Dispatch service calls to target entities."""
+ params = dict(service.data)
+
+ if service.service == SERVICE_SET_CONFIG:
+ async_dispatcher_send(hass, SIGNAL_LOGI_CIRCLE_RECONFIGURE, params)
+ if service.service == SERVICE_LIVESTREAM_SNAPSHOT:
+ async_dispatcher_send(hass, SIGNAL_LOGI_CIRCLE_SNAPSHOT, params)
+ if service.service == SERVICE_LIVESTREAM_RECORD:
+ async_dispatcher_send(hass, SIGNAL_LOGI_CIRCLE_RECORD, params)
+
+ hass.services.async_register(
+ DOMAIN, SERVICE_SET_CONFIG, service_handler,
+ schema=LOGI_CIRCLE_SERVICE_SET_CONFIG)
+
+ hass.services.async_register(
+ DOMAIN, SERVICE_LIVESTREAM_SNAPSHOT, service_handler,
+ schema=LOGI_CIRCLE_SERVICE_SNAPSHOT)
+
+ hass.services.async_register(
+ DOMAIN, SERVICE_LIVESTREAM_RECORD, service_handler,
+ schema=LOGI_CIRCLE_SERVICE_RECORD)
+
+ async def shut_down(event=None):
+ """Close Logi Circle aiohttp session."""
+ await logi_circle.auth_provider.close()
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shut_down)
+
+ return True
+
+
+async def async_unload_entry(hass, entry):
+ """Unload a config entry."""
+ for component in 'camera', 'sensor':
+ await hass.config_entries.async_forward_entry_unload(
+ entry, component)
+
+ logi_circle = hass.data.pop(DATA_LOGI)
+
+ # Tell API wrapper to close all aiohttp sessions, invalidate WS connections
+ # and clear all locally cached tokens
+ await logi_circle.auth_provider.clear_authorization()
+
return True
diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py
index ff6f14431d5..09baaa5ba0b 100644
--- a/homeassistant/components/logi_circle/camera.py
+++ b/homeassistant/components/logi_circle/camera.py
@@ -1,114 +1,87 @@
"""Support to the Logi Circle cameras."""
-import asyncio
from datetime import timedelta
import logging
-import voluptuous as vol
-
from homeassistant.components.camera import (
- ATTR_ENTITY_ID, ATTR_FILENAME, CAMERA_SERVICE_SCHEMA,
- PLATFORM_SCHEMA, SUPPORT_ON_OFF, Camera)
-from homeassistant.components.camera.const import DOMAIN
+ ATTR_ENTITY_ID, SUPPORT_ON_OFF, Camera)
+from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (
- ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL,
- CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON)
-from homeassistant.helpers import config_validation as cv
+ ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, STATE_OFF,
+ STATE_ON)
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from . import ATTRIBUTION, DOMAIN as LOGI_CIRCLE_DOMAIN
-
-DEPENDENCIES = ['logi_circle']
+from .const import (
+ ATTRIBUTION, DOMAIN as LOGI_CIRCLE_DOMAIN, LED_MODE_KEY,
+ RECORDING_MODE_KEY, SIGNAL_LOGI_CIRCLE_RECONFIGURE,
+ SIGNAL_LOGI_CIRCLE_RECORD, SIGNAL_LOGI_CIRCLE_SNAPSHOT)
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
-SERVICE_SET_CONFIG = 'logi_circle_set_config'
-SERVICE_LIVESTREAM_SNAPSHOT = 'logi_circle_livestream_snapshot'
-SERVICE_LIVESTREAM_RECORD = 'logi_circle_livestream_record'
-DATA_KEY = 'camera.logi_circle'
-
-BATTERY_SAVING_MODE_KEY = 'BATTERY_SAVING'
-PRIVACY_MODE_KEY = 'PRIVACY_MODE'
-LED_MODE_KEY = 'LED'
-
-ATTR_MODE = 'mode'
-ATTR_VALUE = 'value'
-ATTR_DURATION = 'duration'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
- cv.time_period,
-})
-
-LOGI_CIRCLE_SERVICE_SET_CONFIG = CAMERA_SERVICE_SCHEMA.extend({
- vol.Required(ATTR_MODE): vol.In([BATTERY_SAVING_MODE_KEY, LED_MODE_KEY,
- PRIVACY_MODE_KEY]),
- vol.Required(ATTR_VALUE): cv.boolean
-})
-
-LOGI_CIRCLE_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({
- vol.Required(ATTR_FILENAME): cv.template
-})
-
-LOGI_CIRCLE_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend({
- vol.Required(ATTR_FILENAME): cv.template,
- vol.Required(ATTR_DURATION): cv.positive_int
-})
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
- """Set up a Logi Circle Camera."""
- devices = hass.data[LOGI_CIRCLE_DOMAIN]
+ """Set up a Logi Circle Camera. Obsolete."""
+ _LOGGER.warning(
+ "Logi Circle no longer works with camera platform configuration")
- cameras = []
- for device in devices:
- cameras.append(LogiCam(device, config))
+
+async def async_setup_entry(hass, entry, async_add_entities):
+ """Set up a Logi Circle Camera based on a config entry."""
+ devices = await hass.data[LOGI_CIRCLE_DOMAIN].cameras
+ ffmpeg = hass.data[DATA_FFMPEG]
+
+ cameras = [LogiCam(device, entry, ffmpeg)
+ for device in devices]
async_add_entities(cameras, True)
- async def service_handler(service):
- """Dispatch service calls to target entities."""
- params = {key: value for key, value in service.data.items()
- if key != ATTR_ENTITY_ID}
- entity_ids = service.data.get(ATTR_ENTITY_ID)
- if entity_ids:
- target_devices = [dev for dev in cameras
- if dev.entity_id in entity_ids]
- else:
- target_devices = cameras
-
- for target_device in target_devices:
- if service.service == SERVICE_SET_CONFIG:
- await target_device.set_config(**params)
- if service.service == SERVICE_LIVESTREAM_SNAPSHOT:
- await target_device.livestream_snapshot(**params)
- if service.service == SERVICE_LIVESTREAM_RECORD:
- await target_device.download_livestream(**params)
-
- hass.services.async_register(
- DOMAIN, SERVICE_SET_CONFIG, service_handler,
- schema=LOGI_CIRCLE_SERVICE_SET_CONFIG)
-
- hass.services.async_register(
- DOMAIN, SERVICE_LIVESTREAM_SNAPSHOT, service_handler,
- schema=LOGI_CIRCLE_SERVICE_SNAPSHOT)
-
- hass.services.async_register(
- DOMAIN, SERVICE_LIVESTREAM_RECORD, service_handler,
- schema=LOGI_CIRCLE_SERVICE_RECORD)
-
class LogiCam(Camera):
"""An implementation of a Logi Circle camera."""
- def __init__(self, camera, device_info):
+ def __init__(self, camera, device_info, ffmpeg):
"""Initialize Logi Circle camera."""
super().__init__()
self._camera = camera
self._name = self._camera.name
self._id = self._camera.mac_address
self._has_battery = self._camera.supports_feature('battery_level')
+ self._ffmpeg = ffmpeg
+ self._listeners = []
+
+ async def async_added_to_hass(self):
+ """Connect camera methods to signals."""
+ def _dispatch_proxy(method):
+ """Expand parameters & filter entity IDs."""
+ async def _call(params):
+ entity_ids = params.get(ATTR_ENTITY_ID)
+ filtered_params = {k: v for k,
+ v in params.items() if k != ATTR_ENTITY_ID}
+ if entity_ids is None or self.entity_id in entity_ids:
+ await method(**filtered_params)
+ return _call
+
+ self._listeners.extend([
+ async_dispatcher_connect(
+ self.hass,
+ SIGNAL_LOGI_CIRCLE_RECONFIGURE,
+ _dispatch_proxy(self.set_config)),
+ async_dispatcher_connect(
+ self.hass,
+ SIGNAL_LOGI_CIRCLE_SNAPSHOT,
+ _dispatch_proxy(self.livestream_snapshot)),
+ async_dispatcher_connect(
+ self.hass,
+ SIGNAL_LOGI_CIRCLE_RECORD,
+ _dispatch_proxy(self.download_livestream)),
+ ])
+
+ async def async_will_remove_from_hass(self):
+ """Disconnect dispatcher listeners when removed."""
+ for detach in self._listeners:
+ detach()
@property
def unique_id(self):
@@ -132,20 +105,19 @@ class LogiCam(Camera):
ATTR_ATTRIBUTION: ATTRIBUTION,
'battery_saving_mode': (
STATE_ON if self._camera.battery_saving else STATE_OFF),
- 'ip_address': self._camera.ip_address,
'microphone_gain': self._camera.microphone_gain
}
# Add battery attributes if camera is battery-powered
if self._has_battery:
- state[ATTR_BATTERY_CHARGING] = self._camera.is_charging
+ state[ATTR_BATTERY_CHARGING] = self._camera.charging
state[ATTR_BATTERY_LEVEL] = self._camera.battery_level
return state
async def async_camera_image(self):
"""Return a still image from the camera."""
- return await self._camera.get_snapshot_image()
+ return await self._camera.live_stream.download_jpeg()
async def async_turn_off(self):
"""Disable streaming mode for this camera."""
@@ -163,11 +135,9 @@ class LogiCam(Camera):
async def set_config(self, mode, value):
"""Set an configuration property for the target camera."""
if mode == LED_MODE_KEY:
- await self._camera.set_led(value)
- if mode == PRIVACY_MODE_KEY:
- await self._camera.set_privacy_mode(value)
- if mode == BATTERY_SAVING_MODE_KEY:
- await self._camera.set_battery_saving_mode(value)
+ await self._camera.set_config('led', value)
+ if mode == RECORDING_MODE_KEY:
+ await self._camera.set_config('recording_disabled', not value)
async def download_livestream(self, filename, duration):
"""Download a recording from the camera's livestream."""
@@ -182,8 +152,10 @@ class LogiCam(Camera):
"Can't write %s, no access to path!", stream_file)
return
- asyncio.shield(self._camera.record_livestream(
- stream_file, timedelta(seconds=duration)), loop=self.hass.loop)
+ await self._camera.live_stream.download_rtsp(
+ filename=stream_file,
+ duration=timedelta(seconds=duration),
+ ffmpeg_bin=self._ffmpeg.binary)
async def livestream_snapshot(self, filename):
"""Download a still frame from the camera's livestream."""
@@ -198,8 +170,9 @@ class LogiCam(Camera):
"Can't write %s, no access to path!", snapshot_file)
return
- asyncio.shield(self._camera.get_livestream_image(
- snapshot_file), loop=self.hass.loop)
+ await self._camera.live_stream.download_jpeg(
+ filename=snapshot_file,
+ refresh=True)
async def async_update(self):
"""Update camera entity and refresh attributes."""
diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py
new file mode 100644
index 00000000000..ba772fb0fed
--- /dev/null
+++ b/homeassistant/components/logi_circle/config_flow.py
@@ -0,0 +1,205 @@
+"""Config flow to configure Logi Circle component."""
+import asyncio
+from collections import OrderedDict
+
+import async_timeout
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant.components.http import HomeAssistantView
+from homeassistant.const import CONF_SENSORS
+from homeassistant.core import callback
+
+from .const import (
+ CONF_API_KEY, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_REDIRECT_URI,
+ DEFAULT_CACHEDB, DOMAIN)
+
+_TIMEOUT = 15 # seconds
+
+DATA_FLOW_IMPL = 'logi_circle_flow_implementation'
+EXTERNAL_ERRORS = 'logi_errors'
+AUTH_CALLBACK_PATH = '/api/logi_circle'
+AUTH_CALLBACK_NAME = 'api:logi_circle'
+
+
+@callback
+def register_flow_implementation(hass, domain, client_id, client_secret,
+ api_key, redirect_uri, sensors):
+ """Register a flow implementation.
+
+ domain: Domain of the component responsible for the implementation.
+ client_id: Client ID.
+ client_secret: Client secret.
+ api_key: API key issued by Logitech.
+ redirect_uri: Auth callback redirect URI.
+ sensors: Sensor config.
+ """
+ if DATA_FLOW_IMPL not in hass.data:
+ hass.data[DATA_FLOW_IMPL] = OrderedDict()
+
+ hass.data[DATA_FLOW_IMPL][domain] = {
+ CONF_CLIENT_ID: client_id,
+ CONF_CLIENT_SECRET: client_secret,
+ CONF_API_KEY: api_key,
+ CONF_REDIRECT_URI: redirect_uri,
+ CONF_SENSORS: sensors,
+ EXTERNAL_ERRORS: None
+ }
+
+
+@config_entries.HANDLERS.register(DOMAIN)
+class LogiCircleFlowHandler(config_entries.ConfigFlow):
+ """Config flow for Logi Circle component."""
+
+ VERSION = 1
+ CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
+
+ def __init__(self):
+ """Initialize flow."""
+ self.flow_impl = None
+
+ async def async_step_import(self, user_input=None):
+ """Handle external yaml configuration."""
+ if self.hass.config_entries.async_entries(DOMAIN):
+ return self.async_abort(reason='already_setup')
+
+ self.flow_impl = DOMAIN
+
+ return await self.async_step_auth()
+
+ async def async_step_user(self, user_input=None):
+ """Handle a flow start."""
+ flows = self.hass.data.get(DATA_FLOW_IMPL, {})
+
+ if self.hass.config_entries.async_entries(DOMAIN):
+ return self.async_abort(reason='already_setup')
+
+ if not flows:
+ return self.async_abort(reason='no_flows')
+
+ if len(flows) == 1:
+ self.flow_impl = list(flows)[0]
+ return await self.async_step_auth()
+
+ if user_input is not None:
+ self.flow_impl = user_input['flow_impl']
+ return await self.async_step_auth()
+
+ return self.async_show_form(
+ step_id='user',
+ data_schema=vol.Schema({
+ vol.Required('flow_impl'):
+ vol.In(list(flows))
+ }))
+
+ async def async_step_auth(self, user_input=None):
+ """Create an entry for auth."""
+ if self.hass.config_entries.async_entries(DOMAIN):
+ return self.async_abort(reason='external_setup')
+
+ external_error = (self.hass.data[DATA_FLOW_IMPL][DOMAIN]
+ [EXTERNAL_ERRORS])
+ errors = {}
+ if external_error:
+ # Handle error from another flow
+ errors['base'] = external_error
+ self.hass.data[DATA_FLOW_IMPL][DOMAIN][EXTERNAL_ERRORS] = None
+ elif user_input is not None:
+ errors['base'] = 'follow_link'
+
+ url = self._get_authorization_url()
+
+ return self.async_show_form(
+ step_id='auth',
+ description_placeholders={'authorization_url': url},
+ errors=errors)
+
+ def _get_authorization_url(self):
+ """Create temporary Circle session and generate authorization url."""
+ from logi_circle import LogiCircle
+ flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl]
+ client_id = flow[CONF_CLIENT_ID]
+ client_secret = flow[CONF_CLIENT_SECRET]
+ api_key = flow[CONF_API_KEY]
+ redirect_uri = flow[CONF_REDIRECT_URI]
+
+ logi_session = LogiCircle(
+ client_id=client_id,
+ client_secret=client_secret,
+ api_key=api_key,
+ redirect_uri=redirect_uri)
+
+ self.hass.http.register_view(LogiCircleAuthCallbackView())
+
+ return logi_session.authorize_url
+
+ async def async_step_code(self, code=None):
+ """Received code for authentication."""
+ if self.hass.config_entries.async_entries(DOMAIN):
+ return self.async_abort(reason='already_setup')
+
+ return await self._async_create_session(code)
+
+ async def _async_create_session(self, code):
+ """Create Logi Circle session and entries."""
+ from logi_circle import LogiCircle
+ from logi_circle.exception import AuthorizationFailed
+
+ flow = self.hass.data[DATA_FLOW_IMPL][DOMAIN]
+ client_id = flow[CONF_CLIENT_ID]
+ client_secret = flow[CONF_CLIENT_SECRET]
+ api_key = flow[CONF_API_KEY]
+ redirect_uri = flow[CONF_REDIRECT_URI]
+ sensors = flow[CONF_SENSORS]
+
+ logi_session = LogiCircle(
+ client_id=client_id,
+ client_secret=client_secret,
+ api_key=api_key,
+ redirect_uri=redirect_uri,
+ cache_file=DEFAULT_CACHEDB)
+
+ try:
+ with async_timeout.timeout(_TIMEOUT, loop=self.hass.loop):
+ await logi_session.authorize(code)
+ except AuthorizationFailed:
+ (self.hass.data[DATA_FLOW_IMPL][DOMAIN]
+ [EXTERNAL_ERRORS]) = 'auth_error'
+ return self.async_abort(reason='external_error')
+ except asyncio.TimeoutError:
+ (self.hass.data[DATA_FLOW_IMPL][DOMAIN]
+ [EXTERNAL_ERRORS]) = 'auth_timeout'
+ return self.async_abort(reason='external_error')
+
+ account_id = (await logi_session.account)['accountId']
+ await logi_session.close()
+ return self.async_create_entry(
+ title='Logi Circle ({})'.format(account_id),
+ data={
+ CONF_CLIENT_ID: client_id,
+ CONF_CLIENT_SECRET: client_secret,
+ CONF_API_KEY: api_key,
+ CONF_REDIRECT_URI: redirect_uri,
+ CONF_SENSORS: sensors})
+
+
+class LogiCircleAuthCallbackView(HomeAssistantView):
+ """Logi Circle Authorization Callback View."""
+
+ requires_auth = False
+ url = AUTH_CALLBACK_PATH
+ name = AUTH_CALLBACK_NAME
+
+ async def get(self, request):
+ """Receive authorization code."""
+ hass = request.app['hass']
+ if 'code' in request.query:
+ hass.async_create_task(
+ hass.config_entries.flow.async_init(
+ DOMAIN,
+ context={'source': 'code'},
+ data=request.query['code'],
+ ))
+ return self.json_message("Authorisation code saved")
+ return self.json_message("Authorisation code missing "
+ "from query string", status_code=400)
diff --git a/homeassistant/components/logi_circle/const.py b/homeassistant/components/logi_circle/const.py
new file mode 100644
index 00000000000..3b32e8139e0
--- /dev/null
+++ b/homeassistant/components/logi_circle/const.py
@@ -0,0 +1,43 @@
+"""Constants in Logi Circle component."""
+
+CONF_CLIENT_ID = 'client_id'
+CONF_CLIENT_SECRET = 'client_secret'
+CONF_API_KEY = 'api_key'
+CONF_REDIRECT_URI = 'redirect_uri'
+
+DEFAULT_CACHEDB = '.logi_cache.pickle'
+
+DOMAIN = 'logi_circle'
+DATA_LOGI = DOMAIN
+
+LED_MODE_KEY = 'LED'
+RECORDING_MODE_KEY = 'RECORDING_MODE'
+
+# Sensor types: Name, unit of measure, icon per sensor key.
+LOGI_SENSORS = {
+ 'battery_level': [
+ 'Battery', '%', 'battery-50'],
+
+ 'last_activity_time': [
+ "Last Activity", None, 'history'],
+
+ 'recording': [
+ 'Recording Mode', None, 'eye'],
+
+ 'signal_strength_category': [
+ "WiFi Signal Category", None, 'wifi'],
+
+ 'signal_strength_percentage': [
+ "WiFi Signal Strength", '%', 'wifi'],
+
+ 'streaming': [
+ 'Streaming Mode', None, 'camera'],
+}
+
+SIGNAL_LOGI_CIRCLE_RECONFIGURE = 'logi_circle_reconfigure'
+SIGNAL_LOGI_CIRCLE_SNAPSHOT = 'logi_circle_snapshot'
+SIGNAL_LOGI_CIRCLE_RECORD = 'logi_circle_record'
+
+# Attribution
+ATTRIBUTION = "Data provided by circle.logi.com"
+DEVICE_BRAND = 'Logitech'
diff --git a/homeassistant/components/logi_circle/manifest.json b/homeassistant/components/logi_circle/manifest.json
new file mode 100644
index 00000000000..8cf6a157a01
--- /dev/null
+++ b/homeassistant/components/logi_circle/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "logi_circle",
+ "name": "Logi Circle",
+ "documentation": "https://www.home-assistant.io/components/logi_circle",
+ "requirements": ["logi_circle==0.2.2"],
+ "dependencies": ["ffmpeg"],
+ "codeowners": ["@evanjd"]
+}
diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py
index 06d1701a9eb..6efd5065ba6 100644
--- a/homeassistant/components/logi_circle/sensor.py
+++ b/homeassistant/components/logi_circle/sensor.py
@@ -1,51 +1,34 @@
"""Support for Logi Circle sensors."""
import logging
-import voluptuous as vol
-
-from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
- ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, CONF_ENTITY_NAMESPACE,
- CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON)
-import homeassistant.helpers.config_validation as cv
+ ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, CONF_MONITORED_CONDITIONS,
+ CONF_SENSORS, STATE_OFF, STATE_ON)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.util.dt import as_local
-from . import (
- ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DOMAIN as LOGI_CIRCLE_DOMAIN)
-
-DEPENDENCIES = ['logi_circle']
+from .const import (
+ ATTRIBUTION, DOMAIN as LOGI_CIRCLE_DOMAIN, LOGI_SENSORS as SENSOR_TYPES)
_LOGGER = logging.getLogger(__name__)
-# Sensor types: Name, unit of measure, icon per sensor key.
-SENSOR_TYPES = {
- 'battery_level': ['Battery', '%', 'battery-50'],
- 'last_activity_time': ['Last Activity', None, 'history'],
- 'privacy_mode': ['Privacy Mode', None, 'eye'],
- 'signal_strength_category': ['WiFi Signal Category', None, 'wifi'],
- 'signal_strength_percentage': ['WiFi Signal Strength', '%', 'wifi'],
- 'speaker_volume': ['Volume', '%', 'volume-high'],
- 'streaming_mode': ['Streaming Mode', None, 'camera'],
-}
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
- cv.string,
- vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
- vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
-})
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
- """Set up a sensor for a Logi Circle device."""
- devices = hass.data[LOGI_CIRCLE_DOMAIN]
+ """Set up a sensor for a Logi Circle device. Obsolete."""
+ _LOGGER.warning(
+ 'Logi Circle no longer works with sensor platform configuration')
+
+
+async def async_setup_entry(hass, entry, async_add_entities):
+ """Set up a Logi Circle sensor based on a config entry."""
+ devices = await hass.data[LOGI_CIRCLE_DOMAIN].cameras
time_zone = str(hass.config.time_zone)
sensors = []
- for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
+ for sensor_type in (entry.data.get(CONF_SENSORS)
+ .get(CONF_MONITORED_CONDITIONS)):
for device in devices:
if device.supports_feature(sensor_type):
sensors.append(LogiSensor(device, time_zone, sensor_type))
@@ -64,6 +47,7 @@ class LogiSensor(Entity):
self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[2])
self._name = "{0} {1}".format(
self._camera.name, SENSOR_TYPES.get(self._sensor_type)[0])
+ self._activity = {}
self._state = None
self._tz = time_zone
@@ -89,12 +73,11 @@ class LogiSensor(Entity):
ATTR_ATTRIBUTION: ATTRIBUTION,
'battery_saving_mode': (
STATE_ON if self._camera.battery_saving else STATE_OFF),
- 'ip_address': self._camera.ip_address,
'microphone_gain': self._camera.microphone_gain
}
if self._sensor_type == 'battery_level':
- state[ATTR_BATTERY_CHARGING] = self._camera.is_charging
+ state[ATTR_BATTERY_CHARGING] = self._camera.charging
return state
@@ -105,9 +88,9 @@ class LogiSensor(Entity):
self._state is not None):
return icon_for_battery_level(battery_level=int(self._state),
charging=False)
- if (self._sensor_type == 'privacy_mode' and
+ if (self._sensor_type == 'recording_mode' and
self._state is not None):
- return 'mdi:eye-off' if self._state == STATE_ON else 'mdi:eye'
+ return 'mdi:eye' if self._state == STATE_ON else 'mdi:eye-off'
if (self._sensor_type == 'streaming_mode' and
self._state is not None):
return (
@@ -125,7 +108,8 @@ class LogiSensor(Entity):
await self._camera.update()
if self._sensor_type == 'last_activity_time':
- last_activity = await self._camera.last_activity
+ last_activity = (await self._camera.
+ get_last_activity(force_refresh=True))
if last_activity is not None:
last_activity_time = as_local(last_activity.end_time_utc)
self._state = '{0:0>2}:{1:0>2}'.format(
@@ -136,3 +120,4 @@ class LogiSensor(Entity):
self._state = STATE_ON if state is True else STATE_OFF
else:
self._state = state
+ self._state = state
diff --git a/homeassistant/components/logi_circle/services.yaml b/homeassistant/components/logi_circle/services.yaml
new file mode 100644
index 00000000000..8d1c7ca1485
--- /dev/null
+++ b/homeassistant/components/logi_circle/services.yaml
@@ -0,0 +1,37 @@
+# Describes the format for available Logi Circle services
+
+set_config:
+ description: Set a configuration property.
+ fields:
+ entity_id:
+ description: Name(s) of entities to apply the operation mode to.
+ example: "camera.living_room_camera"
+ mode:
+ description: "Operation mode. Allowed values: LED, RECORDING_MODE."
+ example: "RECORDING_MODE"
+ value:
+ description: "Operation value. Allowed values: true, false"
+ example: true
+
+livestream_snapshot:
+ description: Take a snapshot from the camera's livestream. Will wake the camera from sleep if required.
+ fields:
+ entity_id:
+ description: Name(s) of entities to create snapshots from.
+ example: "camera.living_room_camera"
+ filename:
+ description: Template of a Filename. Variable is entity_id.
+ example: "/tmp/snapshot_{{ entity_id }}.jpg"
+
+livestream_record:
+ description: Take a video recording from the camera's livestream.
+ fields:
+ entity_id:
+ description: Name(s) of entities to create recordings from.
+ example: "camera.living_room_camera"
+ filename:
+ description: Template of a Filename. Variable is entity_id.
+ example: "/tmp/snapshot_{{ entity_id }}.mp4"
+ duration:
+ description: Recording duration in seconds.
+ example: 60
diff --git a/homeassistant/components/logi_circle/strings.json b/homeassistant/components/logi_circle/strings.json
new file mode 100644
index 00000000000..57dd0b709b7
--- /dev/null
+++ b/homeassistant/components/logi_circle/strings.json
@@ -0,0 +1,32 @@
+{
+ "config": {
+ "title": "Logi Circle",
+ "step": {
+ "user": {
+ "title": "Authentication Provider",
+ "description": "Pick via which authentication provider you want to authenticate with Logi Circle.",
+ "data": {
+ "flow_impl": "Provider"
+ }
+ },
+ "auth": {
+ "title": "Authenticate with Logi Circle",
+ "description": "Please follow the link below and Accept access to your Logi Circle account, then come back and press Submit below.\n\n[Link]({authorization_url})"
+ }
+ },
+ "create_entry": {
+ "default": "Successfully authenticated with Logi Circle."
+ },
+ "error": {
+ "auth_error": "API authorization failed.",
+ "auth_timeout": "Authorization timed out when requesting access token.",
+ "follow_link": "Please follow the link and authenticate before pressing Submit."
+ },
+ "abort": {
+ "already_setup": "You can only configure a single Logi Circle account.",
+ "external_error": "Exception occurred from another flow.",
+ "external_setup": "Logi Circle successfully configured from another flow.",
+ "no_flows": "You need to configure Logi Circle before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/logi_circle/)."
+ }
+ }
+}
diff --git a/homeassistant/components/london_air/manifest.json b/homeassistant/components/london_air/manifest.json
new file mode 100644
index 00000000000..3f0c97edfe0
--- /dev/null
+++ b/homeassistant/components/london_air/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "london_air",
+ "name": "London air",
+ "documentation": "https://www.home-assistant.io/components/london_air",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py
index afb50d766f4..fbdc8966ad0 100644
--- a/homeassistant/components/london_air/sensor.py
+++ b/homeassistant/components/london_air/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for checking the status of London air.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.london_air/
-"""
+"""Sensor for checking the status of London air."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/london_underground/manifest.json b/homeassistant/components/london_underground/manifest.json
new file mode 100644
index 00000000000..5262fa4837e
--- /dev/null
+++ b/homeassistant/components/london_underground/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "london_underground",
+ "name": "London underground",
+ "documentation": "https://www.home-assistant.io/components/london_underground",
+ "requirements": [
+ "london-tube-status==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py
index 1c93d6a1bcb..9bee8569792 100644
--- a/homeassistant/components/london_underground/sensor.py
+++ b/homeassistant/components/london_underground/sensor.py
@@ -1,26 +1,22 @@
-"""
-Sensor for checking the status of London Underground tube lines.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.london_underground/
-"""
-import logging
+"""Sensor for checking the status of London Underground tube lines."""
from datetime import timedelta
+import logging
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import ATTR_ATTRIBUTION
+import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['london-tube-status==0.2']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by TfL Open Data"
CONF_LINE = 'line'
+ICON = 'mdi:subway'
+
SCAN_INTERVAL = timedelta(seconds=30)
TUBE_LINES = [
@@ -59,16 +55,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class LondonTubeSensor(Entity):
- """Sensor that reads the status of a line from TubeData."""
-
- ICON = 'mdi:subway'
+ """Sensor that reads the status of a line from Tube Data."""
def __init__(self, name, data):
- """Initialize the sensor."""
- self._name = name
+ """Initialize the London Underground sensor."""
self._data = data
- self._state = None
self._description = None
+ self._name = name
+ self._state = None
+ self.attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
@property
def name(self):
@@ -83,14 +78,13 @@ class LondonTubeSensor(Entity):
@property
def icon(self):
"""Icon to use in the frontend, if any."""
- return self.ICON
+ return ICON
@property
def device_state_attributes(self):
"""Return other details about the sensor state."""
- attrs = {}
- attrs['Description'] = self._description
- return attrs
+ self.attrs['Description'] = self._description
+ return self.attrs
def update(self):
"""Update the sensor."""
diff --git a/homeassistant/components/loopenergy/manifest.json b/homeassistant/components/loopenergy/manifest.json
new file mode 100644
index 00000000000..b282755b1a0
--- /dev/null
+++ b/homeassistant/components/loopenergy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "loopenergy",
+ "name": "Loopenergy",
+ "documentation": "https://www.home-assistant.io/components/loopenergy",
+ "requirements": [
+ "pyloopenergy==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/loopenergy/sensor.py b/homeassistant/components/loopenergy/sensor.py
index 2ee94249b4c..b2afc36b8f5 100644
--- a/homeassistant/components/loopenergy/sensor.py
+++ b/homeassistant/components/loopenergy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Loop Energy sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.loopenergy/
-"""
+"""Support for Loop Energy sensors."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyloopenergy==0.1.0']
-
CONF_ELEC = 'electricity'
CONF_GAS = 'gas'
diff --git a/homeassistant/components/lovelace/manifest.json b/homeassistant/components/lovelace/manifest.json
new file mode 100644
index 00000000000..1c1a7a107e4
--- /dev/null
+++ b/homeassistant/components/lovelace/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lovelace",
+ "name": "Lovelace",
+ "documentation": "https://www.home-assistant.io/components/lovelace",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/luci/device_tracker.py b/homeassistant/components/luci/device_tracker.py
index f60e8edd8c4..4068be840c8 100644
--- a/homeassistant/components/luci/device_tracker.py
+++ b/homeassistant/components/luci/device_tracker.py
@@ -1,19 +1,13 @@
-"""
-Support for OpenWRT (luci) routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.luci/
-"""
+"""Support for OpenWRT (luci) routers."""
import logging
+
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
- CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SSL)
-
-REQUIREMENTS = ['openwrt-luci-rpc==1.0.5']
+ CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_USERNAME)
+import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -23,7 +17,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean
+ vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
})
@@ -41,10 +35,9 @@ class LuciDeviceScanner(DeviceScanner):
"""Initialize the scanner."""
from openwrt_luci_rpc import OpenWrtRpc
- self.router = OpenWrtRpc(config[CONF_HOST],
- config[CONF_USERNAME],
- config[CONF_PASSWORD],
- config[CONF_SSL])
+ self.router = OpenWrtRpc(
+ config[CONF_HOST], config[CONF_USERNAME], config[CONF_PASSWORD],
+ config[CONF_SSL])
self.last_results = {}
self.success_init = self.router.is_logged_in()
@@ -67,9 +60,9 @@ class LuciDeviceScanner(DeviceScanner):
Get extra attributes of a device.
Some known extra attributes that may be returned in the device tuple
- include Mac Address (mac), Network Device (dev), Ip Address
- (ip), reachable status (reachable), Associated router
- (host), Hostname if known (hostname) among others.
+ include MAC address (mac), network device (dev), IP address
+ (ip), reachable status (reachable), associated router
+ (host), hostname if known (hostname) among others.
"""
device = next((
result for result in self.last_results
@@ -81,8 +74,7 @@ class LuciDeviceScanner(DeviceScanner):
result = self.router.get_all_connected_devices(
only_reachable=True)
- _LOGGER.debug("Luci get_all_connected_devices returned:"
- " %s", result)
+ _LOGGER.debug("Luci get_all_connected_devices returned: %s", result)
last_results = []
for device in result:
diff --git a/homeassistant/components/luci/manifest.json b/homeassistant/components/luci/manifest.json
new file mode 100644
index 00000000000..13b8b172a5d
--- /dev/null
+++ b/homeassistant/components/luci/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "luci",
+ "name": "Luci",
+ "documentation": "https://www.home-assistant.io/components/luci",
+ "requirements": [
+ "openwrt-luci-rpc==1.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": ["@fbradyirl"]
+}
diff --git a/homeassistant/components/luftdaten/.translations/fr.json b/homeassistant/components/luftdaten/.translations/fr.json
index 2aeb29fcf0a..3e1d41be349 100644
--- a/homeassistant/components/luftdaten/.translations/fr.json
+++ b/homeassistant/components/luftdaten/.translations/fr.json
@@ -8,7 +8,8 @@
"step": {
"user": {
"data": {
- "show_on_map": "Montrer sur la carte"
+ "show_on_map": "Montrer sur la carte",
+ "station_id": "ID capteur Luftdaten"
},
"title": "D\u00e9finir Luftdaten"
}
diff --git a/homeassistant/components/luftdaten/__init__.py b/homeassistant/components/luftdaten/__init__.py
index 125cefb9026..81b177f734a 100644
--- a/homeassistant/components/luftdaten/__init__.py
+++ b/homeassistant/components/luftdaten/__init__.py
@@ -17,8 +17,6 @@ from homeassistant.helpers.event import async_track_time_interval
from .config_flow import configured_sensors, duplicate_stations
from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN
-REQUIREMENTS = ['luftdaten==0.3.4']
-
_LOGGER = logging.getLogger(__name__)
DATA_LUFTDATEN = 'luftdaten'
diff --git a/homeassistant/components/luftdaten/config_flow.py b/homeassistant/components/luftdaten/config_flow.py
index b4ebc93da9c..d4baccd006f 100644
--- a/homeassistant/components/luftdaten/config_flow.py
+++ b/homeassistant/components/luftdaten/config_flow.py
@@ -4,7 +4,9 @@ from collections import OrderedDict
import voluptuous as vol
from homeassistant import config_entries
-from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP
+from homeassistant.const import (
+ CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL,
+ CONF_SENSORS, CONF_SHOW_ON_MAP)
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv
@@ -77,6 +79,13 @@ class LuftDatenFlowHandler(config_entries.ConfigFlow):
if not valid:
return self._show_form({CONF_SENSOR_ID: 'invalid_sensor'})
+ available_sensors = [x for x in luftdaten.values
+ if luftdaten.values[x] is not None]
+
+ if available_sensors:
+ user_input.update({
+ CONF_SENSORS: {CONF_MONITORED_CONDITIONS: available_sensors}})
+
scan_interval = user_input.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
user_input.update({CONF_SCAN_INTERVAL: scan_interval.seconds})
diff --git a/homeassistant/components/luftdaten/manifest.json b/homeassistant/components/luftdaten/manifest.json
new file mode 100644
index 00000000000..0e6a46a5c5d
--- /dev/null
+++ b/homeassistant/components/luftdaten/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "luftdaten",
+ "name": "Luftdaten",
+ "documentation": "https://www.home-assistant.io/components/luftdaten",
+ "requirements": [
+ "luftdaten==0.3.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py
index 107673bac45..ca68075df5d 100644
--- a/homeassistant/components/luftdaten/sensor.py
+++ b/homeassistant/components/luftdaten/sensor.py
@@ -14,8 +14,6 @@ from .const import ATTR_SENSOR_ID
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['luftdaten']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/lupusec/__init__.py b/homeassistant/components/lupusec/__init__.py
index 8a5f098f741..e97344f3082 100644
--- a/homeassistant/components/lupusec/__init__.py
+++ b/homeassistant/components/lupusec/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['lupupy==0.0.17']
-
DOMAIN = 'lupusec'
NOTIFICATION_ID = 'lupusec_notification'
diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py
index 0a88f3bd552..9f3e7263396 100644
--- a/homeassistant/components/lupusec/alarm_control_panel.py
+++ b/homeassistant/components/lupusec/alarm_control_panel.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice
-DEPENDENCIES = ['lupusec']
-
ICON = 'mdi:security'
SCAN_INTERVAL = timedelta(seconds=2)
diff --git a/homeassistant/components/lupusec/binary_sensor.py b/homeassistant/components/lupusec/binary_sensor.py
index 2c3f5e0e0b8..28833b3d246 100644
--- a/homeassistant/components/lupusec/binary_sensor.py
+++ b/homeassistant/components/lupusec/binary_sensor.py
@@ -7,8 +7,6 @@ from homeassistant.components.binary_sensor import (
from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice
-DEPENDENCIES = ['lupusec']
-
SCAN_INTERVAL = timedelta(seconds=2)
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/lupusec/manifest.json b/homeassistant/components/lupusec/manifest.json
new file mode 100644
index 00000000000..344ec82d976
--- /dev/null
+++ b/homeassistant/components/lupusec/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lupusec",
+ "name": "Lupusec",
+ "documentation": "https://www.home-assistant.io/components/lupusec",
+ "requirements": [
+ "lupupy==0.0.17"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lupusec/switch.py b/homeassistant/components/lupusec/switch.py
index 0d86ea0a365..b6391959397 100644
--- a/homeassistant/components/lupusec/switch.py
+++ b/homeassistant/components/lupusec/switch.py
@@ -6,8 +6,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice
-DEPENDENCIES = ['lupusec']
-
SCAN_INTERVAL = timedelta(seconds=2)
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/lutron/__init__.py b/homeassistant/components/lutron/__init__.py
index f642e96d8f6..c91103f2244 100644
--- a/homeassistant/components/lutron/__init__.py
+++ b/homeassistant/components/lutron/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
-REQUIREMENTS = ['pylutron==0.2.0']
-
DOMAIN = 'lutron'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py
index da7f69095fc..4a2d72d3116 100644
--- a/homeassistant/components/lutron/cover.py
+++ b/homeassistant/components/lutron/cover.py
@@ -9,8 +9,6 @@ from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Lutron shades."""
diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py
index 5f3fd4787fd..6ddf54e1fc1 100644
--- a/homeassistant/components/lutron/light.py
+++ b/homeassistant/components/lutron/light.py
@@ -8,8 +8,6 @@ from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Lutron lights."""
diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json
new file mode 100644
index 00000000000..b536eef0285
--- /dev/null
+++ b/homeassistant/components/lutron/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lutron",
+ "name": "Lutron",
+ "documentation": "https://www.home-assistant.io/components/lutron",
+ "requirements": [
+ "pylutron==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lutron/scene.py b/homeassistant/components/lutron/scene.py
index a2d18c6d242..05deeef260d 100644
--- a/homeassistant/components/lutron/scene.py
+++ b/homeassistant/components/lutron/scene.py
@@ -7,8 +7,6 @@ from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Lutron scenes."""
diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py
index b42c0d930bc..0b1705fb235 100644
--- a/homeassistant/components/lutron/switch.py
+++ b/homeassistant/components/lutron/switch.py
@@ -7,8 +7,6 @@ from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Lutron switches."""
diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py
index 61c005f60b2..516b5ccd7c8 100644
--- a/homeassistant/components/lutron_caseta/__init__.py
+++ b/homeassistant/components/lutron_caseta/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_HOST
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pylutron-caseta==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
LUTRON_CASETA_SMARTBRIDGE = 'lutron_smartbridge'
diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py
index d970f5282ff..8793fc0236e 100644
--- a/homeassistant/components/lutron_caseta/cover.py
+++ b/homeassistant/components/lutron_caseta/cover.py
@@ -9,8 +9,6 @@ from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron_caseta']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py
index d883da73c91..af93a459031 100644
--- a/homeassistant/components/lutron_caseta/light.py
+++ b/homeassistant/components/lutron_caseta/light.py
@@ -10,8 +10,6 @@ from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron_caseta']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/lutron_caseta/manifest.json b/homeassistant/components/lutron_caseta/manifest.json
new file mode 100644
index 00000000000..4da58cdfc40
--- /dev/null
+++ b/homeassistant/components/lutron_caseta/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lutron_caseta",
+ "name": "Lutron caseta",
+ "documentation": "https://www.home-assistant.io/components/lutron_caseta",
+ "requirements": [
+ "pylutron-caseta==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py
index 2e7059a56fc..df0bb6a7a5a 100644
--- a/homeassistant/components/lutron_caseta/scene.py
+++ b/homeassistant/components/lutron_caseta/scene.py
@@ -7,8 +7,6 @@ from . import LUTRON_CASETA_SMARTBRIDGE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron_caseta']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py
index 54c67091357..0ccf625f765 100644
--- a/homeassistant/components/lutron_caseta/switch.py
+++ b/homeassistant/components/lutron_caseta/switch.py
@@ -7,8 +7,6 @@ from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['lutron_caseta']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/lw12wifi/light.py b/homeassistant/components/lw12wifi/light.py
index 71716b4130d..a2ff77dc2d0 100644
--- a/homeassistant/components/lw12wifi/light.py
+++ b/homeassistant/components/lw12wifi/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Lagute LW-12 WiFi LED Controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.lw12wifi/
-"""
+"""Support for Lagute LW-12 WiFi LED Controller."""
import logging
@@ -21,8 +16,6 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
-REQUIREMENTS = ['lw12==0.9.2']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/lw12wifi/manifest.json b/homeassistant/components/lw12wifi/manifest.json
new file mode 100644
index 00000000000..205072055bb
--- /dev/null
+++ b/homeassistant/components/lw12wifi/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lw12wifi",
+ "name": "Lw12wifi",
+ "documentation": "https://www.home-assistant.io/components/lw12wifi",
+ "requirements": [
+ "lw12==0.9.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lyft/manifest.json b/homeassistant/components/lyft/manifest.json
new file mode 100644
index 00000000000..ff7da7190d9
--- /dev/null
+++ b/homeassistant/components/lyft/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "lyft",
+ "name": "Lyft",
+ "documentation": "https://www.home-assistant.io/components/lyft",
+ "requirements": [
+ "lyft_rides==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/lyft/sensor.py b/homeassistant/components/lyft/sensor.py
index 6fb4a6bf8be..b5788e50b33 100644
--- a/homeassistant/components/lyft/sensor.py
+++ b/homeassistant/components/lyft/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Lyft API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.lyft/
-"""
+"""Support for the Lyft API."""
import logging
from datetime import timedelta
@@ -14,8 +9,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['lyft_rides==0.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
diff --git a/homeassistant/components/magicseaweed/manifest.json b/homeassistant/components/magicseaweed/manifest.json
new file mode 100644
index 00000000000..6534d927f1b
--- /dev/null
+++ b/homeassistant/components/magicseaweed/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "magicseaweed",
+ "name": "Magicseaweed",
+ "documentation": "https://www.home-assistant.io/components/magicseaweed",
+ "requirements": [
+ "magicseaweed==1.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/magicseaweed/sensor.py b/homeassistant/components/magicseaweed/sensor.py
index 0500597b96a..772cfb073c9 100644
--- a/homeassistant/components/magicseaweed/sensor.py
+++ b/homeassistant/components/magicseaweed/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for magicseaweed data from magicseaweed.com.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.magicseaweed/
-"""
+"""Support for magicseaweed data from magicseaweed.com."""
from datetime import timedelta
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['magicseaweed==1.0.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_HOURS = 'hours'
diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py
index 1907a1e9e97..8f851146464 100644
--- a/homeassistant/components/mailbox/__init__.py
+++ b/homeassistant/components/mailbox/__init__.py
@@ -18,7 +18,6 @@ from homeassistant.setup import async_prepare_setup_platform
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
DOMAIN = 'mailbox'
EVENT = 'mailbox_updated'
diff --git a/homeassistant/components/mailbox/manifest.json b/homeassistant/components/mailbox/manifest.json
new file mode 100644
index 00000000000..4ca1db564a4
--- /dev/null
+++ b/homeassistant/components/mailbox/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mailbox",
+ "name": "Mailbox",
+ "documentation": "https://www.home-assistant.io/components/mailbox",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mailgun/.translations/fr.json b/homeassistant/components/mailgun/.translations/fr.json
index 905715de727..5d86a36b947 100644
--- a/homeassistant/components/mailgun/.translations/fr.json
+++ b/homeassistant/components/mailgun/.translations/fr.json
@@ -9,8 +9,10 @@
},
"step": {
"user": {
- "description": "\u00cates-vous s\u00fbr de vouloir configurer Mailgun?"
+ "description": "\u00cates-vous s\u00fbr de vouloir configurer Mailgun?",
+ "title": "Configurer le Webhook Mailgun"
}
- }
+ },
+ "title": "Mailgun"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py
index 3903bd14e25..2f89904f12b 100644
--- a/homeassistant/components/mailgun/__init__.py
+++ b/homeassistant/components/mailgun/__init__.py
@@ -15,7 +15,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_SANDBOX = 'sandbox'
DEFAULT_SANDBOX = False
-DEPENDENCIES = ['webhook']
DOMAIN = 'mailgun'
MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN)
@@ -88,6 +87,11 @@ async def async_unload_entry(hass, entry):
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'Mailgun Webhook',
diff --git a/homeassistant/components/mailgun/manifest.json b/homeassistant/components/mailgun/manifest.json
new file mode 100644
index 00000000000..2979b391ec2
--- /dev/null
+++ b/homeassistant/components/mailgun/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "mailgun",
+ "name": "Mailgun",
+ "documentation": "https://www.home-assistant.io/components/mailgun",
+ "requirements": [
+ "pymailgunner==1.4"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mailgun/notify.py b/homeassistant/components/mailgun/notify.py
index b9f5bf0b100..4709f87b70c 100644
--- a/homeassistant/components/mailgun/notify.py
+++ b/homeassistant/components/mailgun/notify.py
@@ -11,12 +11,8 @@ from homeassistant.const import (
from . import CONF_SANDBOX, DOMAIN as MAILGUN_DOMAIN
-REQUIREMENTS = ['pymailgunner==1.4']
-
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mailgun']
-
# Images to attach to notification
ATTR_IMAGES = 'images'
diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py
index a36a38f596f..14934db41c2 100644
--- a/homeassistant/components/manual/alarm_control_panel.py
+++ b/homeassistant/components/manual/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for manual alarms.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.manual/
-"""
+"""Support for manual alarms."""
import copy
import datetime
import logging
diff --git a/homeassistant/components/manual/manifest.json b/homeassistant/components/manual/manifest.json
new file mode 100644
index 00000000000..6c788971629
--- /dev/null
+++ b/homeassistant/components/manual/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "manual",
+ "name": "Manual",
+ "documentation": "https://www.home-assistant.io/components/manual",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py
index 9bee2b81d61..d952dd68ebb 100644
--- a/homeassistant/components/manual_mqtt/alarm_control_panel.py
+++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for manual alarms controllable via MQTT.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
-"""
+"""Support for manual alarms controllable via MQTT."""
import copy
import datetime
import logging
@@ -88,8 +83,6 @@ def _state_schema(state):
return vol.Schema(schema)
-DEPENDENCIES = ['mqtt']
-
PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Required(CONF_PLATFORM): 'manual_mqtt',
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
diff --git a/homeassistant/components/manual_mqtt/manifest.json b/homeassistant/components/manual_mqtt/manifest.json
new file mode 100644
index 00000000000..81cd1338450
--- /dev/null
+++ b/homeassistant/components/manual_mqtt/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "manual_mqtt",
+ "name": "Manual mqtt",
+ "documentation": "https://www.home-assistant.io/components/manual_mqtt",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/map/manifest.json b/homeassistant/components/map/manifest.json
new file mode 100644
index 00000000000..d26d7d9530f
--- /dev/null
+++ b/homeassistant/components/map/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "map",
+ "name": "Map",
+ "documentation": "https://www.home-assistant.io/components/map",
+ "requirements": [],
+ "dependencies": [
+ "frontend"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/marytts/manifest.json b/homeassistant/components/marytts/manifest.json
new file mode 100644
index 00000000000..5316935c442
--- /dev/null
+++ b/homeassistant/components/marytts/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "marytts",
+ "name": "Marytts",
+ "documentation": "https://www.home-assistant.io/components/marytts",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/marytts/tts.py b/homeassistant/components/marytts/tts.py
index f5d19c977a4..294383cb4dd 100644
--- a/homeassistant/components/marytts/tts.py
+++ b/homeassistant/components/marytts/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the MaryTTS service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts.marytts/
-"""
+"""Support for the MaryTTS service."""
import asyncio
import logging
import re
diff --git a/homeassistant/components/mastodon/manifest.json b/homeassistant/components/mastodon/manifest.json
new file mode 100644
index 00000000000..fd7e023fc91
--- /dev/null
+++ b/homeassistant/components/mastodon/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "mastodon",
+ "name": "Mastodon",
+ "documentation": "https://www.home-assistant.io/components/mastodon",
+ "requirements": [
+ "Mastodon.py==1.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/mastodon/notify.py b/homeassistant/components/mastodon/notify.py
index 6192f6cdca5..d4b78cc4e9f 100644
--- a/homeassistant/components/mastodon/notify.py
+++ b/homeassistant/components/mastodon/notify.py
@@ -1,9 +1,4 @@
-"""
-Mastodon platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.mastodon/
-"""
+"""Mastodon platform for notify component."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['Mastodon.py==1.3.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_BASE_URL = 'base_url'
diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py
index 4b3c1bf4d76..0090d6eb62f 100644
--- a/homeassistant/components/matrix/__init__.py
+++ b/homeassistant/components/matrix/__init__.py
@@ -14,8 +14,6 @@ from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
from homeassistant.util.json import load_json, save_json
from homeassistant.exceptions import HomeAssistantError
-REQUIREMENTS = ['matrix-client==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
SESSION_FILE = '.matrix.conf'
diff --git a/homeassistant/components/matrix/manifest.json b/homeassistant/components/matrix/manifest.json
new file mode 100644
index 00000000000..9ea1a6f0c55
--- /dev/null
+++ b/homeassistant/components/matrix/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "matrix",
+ "name": "Matrix",
+ "documentation": "https://www.home-assistant.io/components/matrix",
+ "requirements": [
+ "matrix-client==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@tinloaf"
+ ]
+}
diff --git a/homeassistant/components/matrix/notify.py b/homeassistant/components/matrix/notify.py
index f1f53268c2b..de2ac3bda2a 100644
--- a/homeassistant/components/matrix/notify.py
+++ b/homeassistant/components/matrix/notify.py
@@ -13,8 +13,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_ROOM = 'default_room'
DOMAIN = 'matrix'
-DEPENDENCIES = [DOMAIN]
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEFAULT_ROOM): cv.string,
})
diff --git a/homeassistant/components/matrix/services.yaml b/homeassistant/components/matrix/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/maxcube/__init__.py b/homeassistant/components/maxcube/__init__.py
index c398ccbde4f..12a6fda2cc3 100644
--- a/homeassistant/components/maxcube/__init__.py
+++ b/homeassistant/components/maxcube/__init__.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SCAN_INTERVAL
-REQUIREMENTS = ['maxcube-api==0.1.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_PORT = 62910
diff --git a/homeassistant/components/maxcube/manifest.json b/homeassistant/components/maxcube/manifest.json
new file mode 100644
index 00000000000..a28096c5eb7
--- /dev/null
+++ b/homeassistant/components/maxcube/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "maxcube",
+ "name": "Maxcube",
+ "documentation": "https://www.home-assistant.io/components/maxcube",
+ "requirements": [
+ "maxcube-api==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py
index 67dd06efab8..98f03cd8fd0 100644
--- a/homeassistant/components/media_extractor/__init__.py
+++ b/homeassistant/components/media_extractor/__init__.py
@@ -12,15 +12,12 @@ from homeassistant.const import (
ATTR_ENTITY_ID)
from homeassistant.helpers import config_validation as cv
-REQUIREMENTS = ['youtube_dl==2019.03.18']
-
_LOGGER = logging.getLogger(__name__)
CONF_CUSTOMIZE_ENTITIES = 'customize'
CONF_DEFAULT_STREAM_QUERY = 'default_query'
DEFAULT_STREAM_QUERY = 'best'
-DEPENDENCIES = ['media_player']
DOMAIN = 'media_extractor'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json
new file mode 100644
index 00000000000..431e711951a
--- /dev/null
+++ b/homeassistant/components/media_extractor/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "media_extractor",
+ "name": "Media extractor",
+ "documentation": "https://www.home-assistant.io/components/media_extractor",
+ "requirements": [
+ "youtube_dl==2019.04.07"
+ ],
+ "dependencies": [
+ "media_player"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/media_extractor/services.yaml b/homeassistant/components/media_extractor/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py
index ad29a645765..7dcfdac5217 100644
--- a/homeassistant/components/media_player/__init__.py
+++ b/homeassistant/components/media_player/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to interface with various media players.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/media_player/
-"""
+"""Component to interface with various media players."""
import asyncio
import base64
import collections
@@ -37,58 +32,25 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import bind_hass
from .const import (
- ATTR_APP_ID,
- ATTR_APP_NAME,
- ATTR_INPUT_SOURCE,
- ATTR_INPUT_SOURCE_LIST,
- ATTR_MEDIA_ALBUM_ARTIST,
- ATTR_MEDIA_ALBUM_NAME,
- ATTR_MEDIA_ARTIST,
- ATTR_MEDIA_CHANNEL,
- ATTR_MEDIA_CONTENT_ID,
- ATTR_MEDIA_CONTENT_TYPE,
- ATTR_MEDIA_DURATION,
- ATTR_MEDIA_ENQUEUE,
- ATTR_MEDIA_EPISODE,
- ATTR_MEDIA_PLAYLIST,
- ATTR_MEDIA_POSITION,
- ATTR_MEDIA_POSITION_UPDATED_AT,
- ATTR_MEDIA_SEASON,
- ATTR_MEDIA_SEEK_POSITION,
- ATTR_MEDIA_SERIES_TITLE,
- ATTR_MEDIA_SHUFFLE,
- ATTR_MEDIA_TITLE,
- ATTR_MEDIA_TRACK,
- ATTR_MEDIA_VOLUME_LEVEL,
- ATTR_MEDIA_VOLUME_MUTED,
- ATTR_SOUND_MODE,
- ATTR_SOUND_MODE_LIST,
- DOMAIN,
- SERVICE_CLEAR_PLAYLIST,
- SERVICE_PLAY_MEDIA,
- SERVICE_SELECT_SOUND_MODE,
- SERVICE_SELECT_SOURCE,
- SUPPORT_PAUSE,
- SUPPORT_SEEK,
- SUPPORT_VOLUME_SET,
- SUPPORT_VOLUME_MUTE,
- SUPPORT_PREVIOUS_TRACK,
- SUPPORT_NEXT_TRACK,
- SUPPORT_PLAY_MEDIA,
- SUPPORT_SELECT_SOURCE,
- SUPPORT_STOP,
- SUPPORT_CLEAR_PLAYLIST,
- SUPPORT_PLAY,
- SUPPORT_SHUFFLE_SET,
- SUPPORT_SELECT_SOUND_MODE,
-)
+ ATTR_APP_ID, ATTR_APP_NAME, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST,
+ ATTR_MEDIA_ALBUM_ARTIST, ATTR_MEDIA_ALBUM_NAME, ATTR_MEDIA_ARTIST,
+ ATTR_MEDIA_CHANNEL, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE,
+ ATTR_MEDIA_DURATION, ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_EPISODE,
+ ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT,
+ ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SERIES_TITLE,
+ ATTR_MEDIA_SHUFFLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK,
+ ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE,
+ ATTR_SOUND_MODE_LIST, DOMAIN, SERVICE_CLEAR_PLAYLIST, SERVICE_PLAY_MEDIA,
+ SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST,
+ SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
+ SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOUND_MODE,
+ SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF,
+ SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
from .reproduce_state import async_reproduce_states # noqa
_LOGGER = logging.getLogger(__name__)
_RND = SystemRandom()
-DEPENDENCIES = ['http']
-
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ENTITY_IMAGE_URL = '/api/media_player_proxy/{0}?token={1}&cache={2}'
@@ -202,74 +164,77 @@ async def async_setup(hass, config):
component.async_register_entity_service(
SERVICE_TURN_ON, MEDIA_PLAYER_SCHEMA,
- 'async_turn_on'
+ 'async_turn_on', SUPPORT_TURN_ON
)
component.async_register_entity_service(
SERVICE_TURN_OFF, MEDIA_PLAYER_SCHEMA,
- 'async_turn_off'
+ 'async_turn_off', SUPPORT_TURN_OFF
)
component.async_register_entity_service(
SERVICE_TOGGLE, MEDIA_PLAYER_SCHEMA,
- 'async_toggle'
+ 'async_toggle', SUPPORT_TURN_OFF | SUPPORT_TURN_ON
)
component.async_register_entity_service(
SERVICE_VOLUME_UP, MEDIA_PLAYER_SCHEMA,
- 'async_volume_up'
+ 'async_volume_up', SUPPORT_VOLUME_SET
)
component.async_register_entity_service(
SERVICE_VOLUME_DOWN, MEDIA_PLAYER_SCHEMA,
- 'async_volume_down'
+ 'async_volume_down', SUPPORT_VOLUME_SET
)
component.async_register_entity_service(
SERVICE_MEDIA_PLAY_PAUSE, MEDIA_PLAYER_SCHEMA,
- 'async_media_play_pause'
+ 'async_media_play_pause', SUPPORT_PLAY | SUPPORT_PAUSE
)
component.async_register_entity_service(
SERVICE_MEDIA_PLAY, MEDIA_PLAYER_SCHEMA,
- 'async_media_play'
+ 'async_media_play', SUPPORT_PLAY
)
component.async_register_entity_service(
SERVICE_MEDIA_PAUSE, MEDIA_PLAYER_SCHEMA,
- 'async_media_pause'
+ 'async_media_pause', SUPPORT_PAUSE
)
component.async_register_entity_service(
SERVICE_MEDIA_STOP, MEDIA_PLAYER_SCHEMA,
- 'async_media_stop'
+ 'async_media_stop', SUPPORT_STOP
)
component.async_register_entity_service(
SERVICE_MEDIA_NEXT_TRACK, MEDIA_PLAYER_SCHEMA,
- 'async_media_next_track'
+ 'async_media_next_track', SUPPORT_NEXT_TRACK
)
component.async_register_entity_service(
SERVICE_MEDIA_PREVIOUS_TRACK, MEDIA_PLAYER_SCHEMA,
- 'async_media_previous_track'
+ 'async_media_previous_track', SUPPORT_PREVIOUS_TRACK
)
component.async_register_entity_service(
SERVICE_CLEAR_PLAYLIST, MEDIA_PLAYER_SCHEMA,
- 'async_clear_playlist'
+ 'async_clear_playlist', SUPPORT_CLEAR_PLAYLIST
)
component.async_register_entity_service(
SERVICE_VOLUME_SET, MEDIA_PLAYER_SET_VOLUME_SCHEMA,
lambda entity, call: entity.async_set_volume_level(
- volume=call.data[ATTR_MEDIA_VOLUME_LEVEL])
+ volume=call.data[ATTR_MEDIA_VOLUME_LEVEL]),
+ SUPPORT_VOLUME_SET
)
component.async_register_entity_service(
SERVICE_VOLUME_MUTE, MEDIA_PLAYER_MUTE_VOLUME_SCHEMA,
lambda entity, call: entity.async_mute_volume(
- mute=call.data[ATTR_MEDIA_VOLUME_MUTED])
+ mute=call.data[ATTR_MEDIA_VOLUME_MUTED]),
+ SUPPORT_VOLUME_MUTE
)
component.async_register_entity_service(
SERVICE_MEDIA_SEEK, MEDIA_PLAYER_MEDIA_SEEK_SCHEMA,
lambda entity, call: entity.async_media_seek(
- position=call.data[ATTR_MEDIA_SEEK_POSITION])
+ position=call.data[ATTR_MEDIA_SEEK_POSITION]),
+ SUPPORT_SEEK
)
component.async_register_entity_service(
SERVICE_SELECT_SOURCE, MEDIA_PLAYER_SELECT_SOURCE_SCHEMA,
- 'async_select_source'
+ 'async_select_source', SUPPORT_SELECT_SOURCE
)
component.async_register_entity_service(
SERVICE_SELECT_SOUND_MODE, MEDIA_PLAYER_SELECT_SOUND_MODE_SCHEMA,
- 'async_select_sound_mode'
+ 'async_select_sound_mode', SUPPORT_SELECT_SOUND_MODE
)
component.async_register_entity_service(
SERVICE_PLAY_MEDIA, MEDIA_PLAYER_PLAY_MEDIA_SCHEMA,
@@ -277,11 +242,11 @@ async def async_setup(hass, config):
media_type=call.data[ATTR_MEDIA_CONTENT_TYPE],
media_id=call.data[ATTR_MEDIA_CONTENT_ID],
enqueue=call.data.get(ATTR_MEDIA_ENQUEUE)
- )
+ ), SUPPORT_PLAY_MEDIA
)
component.async_register_entity_service(
SERVICE_SHUFFLE_SET, MEDIA_PLAYER_SET_SHUFFLE_SCHEMA,
- 'async_set_shuffle'
+ 'async_set_shuffle', SUPPORT_SHUFFLE_SET
)
return True
diff --git a/homeassistant/components/media_player/manifest.json b/homeassistant/components/media_player/manifest.json
new file mode 100644
index 00000000000..bf6f8fabafa
--- /dev/null
+++ b/homeassistant/components/media_player/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "media_player",
+ "name": "Media player",
+ "documentation": "https://www.home-assistant.io/components/media_player",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mediaroom/manifest.json b/homeassistant/components/mediaroom/manifest.json
new file mode 100644
index 00000000000..134d85fa171
--- /dev/null
+++ b/homeassistant/components/mediaroom/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "mediaroom",
+ "name": "Mediaroom",
+ "documentation": "https://www.home-assistant.io/components/mediaroom",
+ "requirements": [
+ "pymediaroom==0.6.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/mediaroom/media_player.py b/homeassistant/components/mediaroom/media_player.py
index 29cc7332936..75aa20daf82 100644
--- a/homeassistant/components/mediaroom/media_player.py
+++ b/homeassistant/components/mediaroom/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for the Mediaroom Set-up-box.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.mediaroom/
-"""
+"""Support for the Mediaroom Set-up-box."""
import logging
import voluptuous as vol
@@ -24,8 +19,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
-REQUIREMENTS = ['pymediaroom==0.6.4']
-
_LOGGER = logging.getLogger(__name__)
DATA_MEDIAROOM = 'mediaroom_known_stb'
diff --git a/homeassistant/components/melissa/__init__.py b/homeassistant/components/melissa/__init__.py
index 2037caa11c3..14ecfadb5bf 100644
--- a/homeassistant/components/melissa/__init__.py
+++ b/homeassistant/components/melissa/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ["py-melissa-climate==2.0.0"]
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'melissa'
diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py
index 0df294a148d..8d834691b12 100644
--- a/homeassistant/components/melissa/climate.py
+++ b/homeassistant/components/melissa/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for Melissa Climate A/C.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/climate.melissa/
-"""
+"""Support for Melissa Climate A/C."""
import logging
from homeassistant.components.climate import ClimateDevice
@@ -18,8 +13,6 @@ from homeassistant.const import (
from . import DATA_MELISSA
-DEPENDENCIES = ['melissa']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
diff --git a/homeassistant/components/melissa/manifest.json b/homeassistant/components/melissa/manifest.json
new file mode 100644
index 00000000000..f9fa1cab502
--- /dev/null
+++ b/homeassistant/components/melissa/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "melissa",
+ "name": "Melissa",
+ "documentation": "https://www.home-assistant.io/components/melissa",
+ "requirements": [
+ "py-melissa-climate==2.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@kennedyshead"
+ ]
+}
diff --git a/homeassistant/components/meraki/device_tracker.py b/homeassistant/components/meraki/device_tracker.py
index d12aff1127a..edca1fbd494 100644
--- a/homeassistant/components/meraki/device_tracker.py
+++ b/homeassistant/components/meraki/device_tracker.py
@@ -18,7 +18,6 @@ from homeassistant.components.device_tracker import (
CONF_VALIDATOR = 'validator'
CONF_SECRET = 'secret'
-DEPENDENCIES = ['http']
URL = '/api/meraki'
VERSION = '2.0'
diff --git a/homeassistant/components/meraki/manifest.json b/homeassistant/components/meraki/manifest.json
new file mode 100644
index 00000000000..d03679ed41e
--- /dev/null
+++ b/homeassistant/components/meraki/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "meraki",
+ "name": "Meraki",
+ "documentation": "https://www.home-assistant.io/components/meraki",
+ "requirements": [],
+ "dependencies": ["http"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/message_bird/manifest.json b/homeassistant/components/message_bird/manifest.json
new file mode 100644
index 00000000000..a6c49b3c396
--- /dev/null
+++ b/homeassistant/components/message_bird/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "message_bird",
+ "name": "Message bird",
+ "documentation": "https://www.home-assistant.io/components/message_bird",
+ "requirements": [
+ "messagebird==1.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/message_bird/notify.py b/homeassistant/components/message_bird/notify.py
index cfb22ff1d52..eecd563dc53 100644
--- a/homeassistant/components/message_bird/notify.py
+++ b/homeassistant/components/message_bird/notify.py
@@ -1,9 +1,4 @@
-"""
-MessageBird platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.message_bird/
-"""
+"""MessageBird platform for notify component."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['messagebird==1.2.0']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/met/manifest.json b/homeassistant/components/met/manifest.json
new file mode 100644
index 00000000000..b2ef166be50
--- /dev/null
+++ b/homeassistant/components/met/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "met",
+ "name": "Met",
+ "documentation": "https://www.home-assistant.io/components/met",
+ "requirements": [
+ "pyMetno==0.4.6"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py
index 6c9613ac5d2..d9824e203c5 100644
--- a/homeassistant/components/met/weather.py
+++ b/homeassistant/components/met/weather.py
@@ -13,8 +13,6 @@ from homeassistant.helpers.event import (
async_call_later, async_track_utc_time_change)
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pyMetno==0.4.6']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Weather forecast from met.no, delivered by the Norwegian " \
diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py
index e084cff3c79..df0292ec407 100644
--- a/homeassistant/components/meteo_france/__init__.py
+++ b/homeassistant/components/meteo_france/__init__.py
@@ -9,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
-REQUIREMENTS = ['meteofrance==0.3.4']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Météo-France"
diff --git a/homeassistant/components/meteo_france/manifest.json b/homeassistant/components/meteo_france/manifest.json
new file mode 100644
index 00000000000..20ad5e46fe6
--- /dev/null
+++ b/homeassistant/components/meteo_france/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "meteo_france",
+ "name": "Meteo france",
+ "documentation": "https://www.home-assistant.io/components/meteo_france",
+ "requirements": [
+ "meteofrance==0.3.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/metoffice/manifest.json b/homeassistant/components/metoffice/manifest.json
new file mode 100644
index 00000000000..f5d358854f6
--- /dev/null
+++ b/homeassistant/components/metoffice/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "metoffice",
+ "name": "Metoffice",
+ "documentation": "https://www.home-assistant.io/components/metoffice",
+ "requirements": [
+ "datapoint==0.4.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py
index 3d9c9485da3..ff334823ec6 100644
--- a/homeassistant/components/metoffice/sensor.py
+++ b/homeassistant/components/metoffice/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for UK Met Office weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.metoffice/
-"""
+"""Support for UK Met Office weather service."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['datapoint==0.4.3']
-
ATTR_LAST_UPDATE = 'last_update'
ATTR_SENSOR_ID = 'sensor_id'
ATTR_SITE_ID = 'site_id'
diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py
index a67dcdcdbd6..409fc099122 100644
--- a/homeassistant/components/metoffice/weather.py
+++ b/homeassistant/components/metoffice/weather.py
@@ -10,8 +10,6 @@ from homeassistant.helpers import config_validation as cv
from .sensor import ATTRIBUTION, CONDITION_CLASSES, MetOfficeCurrentData
-REQUIREMENTS = ['datapoint==0.4.3']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Met Office"
diff --git a/homeassistant/components/mfi/manifest.json b/homeassistant/components/mfi/manifest.json
new file mode 100644
index 00000000000..1e84b39a366
--- /dev/null
+++ b/homeassistant/components/mfi/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mfi",
+ "name": "Mfi",
+ "documentation": "https://www.home-assistant.io/components/mfi",
+ "requirements": [
+ "mficlient==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mfi/sensor.py b/homeassistant/components/mfi/sensor.py
index 44e5dbda84f..49ec86c93cd 100644
--- a/homeassistant/components/mfi/sensor.py
+++ b/homeassistant/components/mfi/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Ubiquiti mFi sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mfi/
-"""
+"""Support for Ubiquiti mFi sensors."""
import logging
import requests
@@ -16,8 +11,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['mficlient==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSL = True
diff --git a/homeassistant/components/mfi/switch.py b/homeassistant/components/mfi/switch.py
index 521230ccbd5..7b51813589d 100644
--- a/homeassistant/components/mfi/switch.py
+++ b/homeassistant/components/mfi/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Ubiquiti mFi switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.mfi/
-"""
+"""Support for Ubiquiti mFi switches."""
import logging
import requests
@@ -15,8 +10,6 @@ from homeassistant.const import (
CONF_VERIFY_SSL)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['mficlient==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSL = True
diff --git a/homeassistant/components/mhz19/manifest.json b/homeassistant/components/mhz19/manifest.json
new file mode 100644
index 00000000000..8545db90e27
--- /dev/null
+++ b/homeassistant/components/mhz19/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mhz19",
+ "name": "Mhz19",
+ "documentation": "https://www.home-assistant.io/components/mhz19",
+ "requirements": [
+ "pmsensor==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mhz19/sensor.py b/homeassistant/components/mhz19/sensor.py
index dd8a0395088..16e9da304a7 100644
--- a/homeassistant/components/mhz19/sensor.py
+++ b/homeassistant/components/mhz19/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for CO2 sensor connected to a serial port.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mhz19/
-"""
+"""Support for CO2 sensor connected to a serial port."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.util.temperature import celsius_to_fahrenheit
from homeassistant.util import Throttle
-REQUIREMENTS = ['pmsensor==0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_SERIAL_DEVICE = 'serial_device'
diff --git a/homeassistant/components/microsoft/manifest.json b/homeassistant/components/microsoft/manifest.json
new file mode 100644
index 00000000000..827d961a093
--- /dev/null
+++ b/homeassistant/components/microsoft/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "microsoft",
+ "name": "Microsoft",
+ "documentation": "https://www.home-assistant.io/components/microsoft",
+ "requirements": [
+ "pycsspeechtts==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/microsoft/tts.py b/homeassistant/components/microsoft/tts.py
index 55cf7a4ae7a..39bd1186b76 100644
--- a/homeassistant/components/microsoft/tts.py
+++ b/homeassistant/components/microsoft/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the Microsoft Cognitive Services text-to-speech service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts.microsoft/
-"""
+"""Support for the Microsoft Cognitive Services text-to-speech service."""
from http.client import HTTPException
import logging
@@ -20,8 +15,6 @@ CONF_VOLUME = 'volume'
CONF_PITCH = 'pitch'
CONF_CONTOUR = 'contour'
-REQUIREMENTS = ["pycsspeechtts==1.0.2"]
-
_LOGGER = logging.getLogger(__name__)
SUPPORTED_LANGUAGES = [
diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py
index 9b3ee960fb2..25b74698da6 100644
--- a/homeassistant/components/microsoft_face/__init__.py
+++ b/homeassistant/components/microsoft_face/__init__.py
@@ -25,7 +25,6 @@ CONF_AZURE_REGION = 'azure_region'
DATA_MICROSOFT_FACE = 'microsoft_face'
DEFAULT_TIMEOUT = 10
-DEPENDENCIES = ['camera']
DOMAIN = 'microsoft_face'
FACE_API_URL = "api.cognitive.microsoft.com/face/v1.0/{0}"
diff --git a/homeassistant/components/microsoft_face/manifest.json b/homeassistant/components/microsoft_face/manifest.json
new file mode 100644
index 00000000000..7f6c4fbd935
--- /dev/null
+++ b/homeassistant/components/microsoft_face/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "microsoft_face",
+ "name": "Microsoft face",
+ "documentation": "https://www.home-assistant.io/components/microsoft_face",
+ "requirements": [],
+ "dependencies": [
+ "camera"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/microsoft_face_detect/image_processing.py b/homeassistant/components/microsoft_face_detect/image_processing.py
index ae6b9c260cd..addcea21c86 100644
--- a/homeassistant/components/microsoft_face_detect/image_processing.py
+++ b/homeassistant/components/microsoft_face_detect/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will help set the Microsoft face detect processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.microsoft_face_detect/
-"""
+"""Component that will help set the Microsoft face detect processing."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.core import split_entity_id
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
-DEPENDENCIES = ['microsoft_face']
-
_LOGGER = logging.getLogger(__name__)
SUPPORTED_ATTRIBUTES = [
diff --git a/homeassistant/components/microsoft_face_detect/manifest.json b/homeassistant/components/microsoft_face_detect/manifest.json
new file mode 100644
index 00000000000..b272a299cf5
--- /dev/null
+++ b/homeassistant/components/microsoft_face_detect/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "microsoft_face_detect",
+ "name": "Microsoft face detect",
+ "documentation": "https://www.home-assistant.io/components/microsoft_face_detect",
+ "requirements": [],
+ "dependencies": ["microsoft_face"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/microsoft_face_identify/image_processing.py b/homeassistant/components/microsoft_face_identify/image_processing.py
index 7a427d9b046..055778be311 100644
--- a/homeassistant/components/microsoft_face_identify/image_processing.py
+++ b/homeassistant/components/microsoft_face_identify/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will help set the Microsoft face for verify processing.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/image_processing.microsoft_face_identify/
-"""
+"""Component that will help set the Microsoft face for verify processing."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.core import split_entity_id
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
-DEPENDENCIES = ['microsoft_face']
-
_LOGGER = logging.getLogger(__name__)
CONF_GROUP = 'group'
diff --git a/homeassistant/components/microsoft_face_identify/manifest.json b/homeassistant/components/microsoft_face_identify/manifest.json
new file mode 100644
index 00000000000..10e4bde103c
--- /dev/null
+++ b/homeassistant/components/microsoft_face_identify/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "microsoft_face_identify",
+ "name": "Microsoft face identify",
+ "documentation": "https://www.home-assistant.io/components/microsoft_face_identify",
+ "requirements": [],
+ "dependencies": ["microsoft_face"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/miflora/manifest.json b/homeassistant/components/miflora/manifest.json
new file mode 100644
index 00000000000..d4e7a333acf
--- /dev/null
+++ b/homeassistant/components/miflora/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "miflora",
+ "name": "Miflora",
+ "documentation": "https://www.home-assistant.io/components/miflora",
+ "requirements": [
+ "miflora==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen",
+ "@ChristianKuehnel"
+ ]
+}
diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py
index 91f873f5a2d..0a8a51e0e80 100644
--- a/homeassistant/components/miflora/sensor.py
+++ b/homeassistant/components/miflora/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Xiaomi Mi Flora BLE plant sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.miflora/
-"""
+"""Support for Xiaomi Mi Flora BLE plant sensor."""
from datetime import timedelta
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START)
from homeassistant.core import callback
-REQUIREMENTS = ['miflora==0.4.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_ADAPTER = 'adapter'
diff --git a/homeassistant/components/mikrotik/device_tracker.py b/homeassistant/components/mikrotik/device_tracker.py
index c4635f8dc43..3709bc476f5 100644
--- a/homeassistant/components/mikrotik/device_tracker.py
+++ b/homeassistant/components/mikrotik/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Mikrotik routers as device tracker.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.mikrotik/
-"""
+"""Support for Mikrotik routers as device tracker."""
import logging
import ssl
@@ -16,20 +11,22 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL, CONF_METHOD)
-REQUIREMENTS = ['librouteros==2.2.0']
-
_LOGGER = logging.getLogger(__name__)
MTK_DEFAULT_API_PORT = '8728'
MTK_DEFAULT_API_SSL_PORT = '8729'
+CONF_ENCODING = 'encoding'
+DEFAULT_ENCODING = 'utf-8'
+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_METHOD): cv.string,
vol.Optional(CONF_PORT): cv.port,
- vol.Optional(CONF_SSL, default=False): cv.boolean
+ vol.Optional(CONF_SSL, default=False): cv.boolean,
+ vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
})
@@ -58,6 +55,7 @@ class MikrotikScanner(DeviceScanner):
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.method = config.get(CONF_METHOD)
+ self.encoding = config[CONF_ENCODING]
self.connected = False
self.success_init = False
@@ -77,7 +75,7 @@ class MikrotikScanner(DeviceScanner):
try:
kwargs = {
'port': self.port,
- 'encoding': 'utf-8'
+ 'encoding': self.encoding
}
if self.ssl:
ssl_context = ssl.create_default_context()
diff --git a/homeassistant/components/mikrotik/manifest.json b/homeassistant/components/mikrotik/manifest.json
new file mode 100644
index 00000000000..caa9733f241
--- /dev/null
+++ b/homeassistant/components/mikrotik/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mikrotik",
+ "name": "Mikrotik",
+ "documentation": "https://www.home-assistant.io/components/mikrotik",
+ "requirements": [
+ "librouteros==2.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py
index 6867f57ee48..43877a1f818 100644
--- a/homeassistant/components/mill/climate.py
+++ b/homeassistant/components/mill/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for mill wifi-enabled home heaters.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.mill/
-"""
+"""Support for mill wifi-enabled home heaters."""
import logging
@@ -20,8 +15,6 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['millheater==0.3.4']
-
_LOGGER = logging.getLogger(__name__)
ATTR_AWAY_TEMP = 'away_temp'
diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json
new file mode 100644
index 00000000000..05efb845c12
--- /dev/null
+++ b/homeassistant/components/mill/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "mill",
+ "name": "Mill",
+ "documentation": "https://www.home-assistant.io/components/mill",
+ "requirements": [
+ "millheater==0.3.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/mill/services.yaml b/homeassistant/components/mill/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/min_max/manifest.json b/homeassistant/components/min_max/manifest.json
new file mode 100644
index 00000000000..ea6befe498b
--- /dev/null
+++ b/homeassistant/components/min_max/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "min_max",
+ "name": "Min max",
+ "documentation": "https://www.home-assistant.io/components/min_max",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py
index e18c67471d9..929ef42a2d5 100644
--- a/homeassistant/components/min_max/sensor.py
+++ b/homeassistant/components/min_max/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for displaying the minimal and the maximal value.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.min_max/
-"""
+"""Support for displaying the minimal and the maximal value."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/mitemp_bt/manifest.json b/homeassistant/components/mitemp_bt/manifest.json
new file mode 100644
index 00000000000..2324a861b38
--- /dev/null
+++ b/homeassistant/components/mitemp_bt/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mitemp_bt",
+ "name": "Mitemp bt",
+ "documentation": "https://www.home-assistant.io/components/mitemp_bt",
+ "requirements": [
+ "mitemp_bt==0.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mitemp_bt/sensor.py b/homeassistant/components/mitemp_bt/sensor.py
index f8bee17978d..c2afaecf789 100644
--- a/homeassistant/components/mitemp_bt/sensor.py
+++ b/homeassistant/components/mitemp_bt/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Xiaomi Mi Temp BLE environmental sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mitemp_bt/
-"""
+"""Support for Xiaomi Mi Temp BLE environmental sensor."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.const import (
)
-REQUIREMENTS = ['mitemp_bt==0.0.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ADAPTER = 'adapter'
diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py
index 0e73dfac0b7..b9aa9c3e186 100644
--- a/homeassistant/components/mjpeg/camera.py
+++ b/homeassistant/components/mjpeg/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for IP Cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.mjpeg/
-"""
+"""Support for IP Cameras."""
import asyncio
import logging
from contextlib import closing
diff --git a/homeassistant/components/mjpeg/manifest.json b/homeassistant/components/mjpeg/manifest.json
new file mode 100644
index 00000000000..2ecd66910be
--- /dev/null
+++ b/homeassistant/components/mjpeg/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "mjpeg",
+ "name": "Mjpeg",
+ "documentation": "https://www.home-assistant.io/components/mjpeg",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mobile_app/.translations/es.json b/homeassistant/components/mobile_app/.translations/es.json
new file mode 100644
index 00000000000..e88012b8613
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/es.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "install_app": "Abre la aplicaci\u00f3n en el m\u00f3vil para configurar la integraci\u00f3n con Home Assistant. Echa un vistazo a [la documentaci\u00f3n]({apps_url}) para ver una lista de apps compatibles."
+ },
+ "step": {
+ "confirm": {
+ "description": "\u00bfQuieres configurar el componente de la aplicaci\u00f3n para el m\u00f3vil?",
+ "title": "Aplicaci\u00f3n para el m\u00f3vil"
+ }
+ },
+ "title": "Aplicaci\u00f3n para el m\u00f3vil"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/.translations/fr.json b/homeassistant/components/mobile_app/.translations/fr.json
new file mode 100644
index 00000000000..54c945a7a4b
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/fr.json
@@ -0,0 +1,14 @@
+{
+ "config": {
+ "abort": {
+ "install_app": "Ouvrez l'application mobile pour configurer l'int\u00e9gration avec Home Assistant. Voir [la documentation] ( {apps_url} ) pour obtenir une liste des applications compatibles."
+ },
+ "step": {
+ "confirm": {
+ "description": "Voulez-vous configurer le composant Application mobile?",
+ "title": "Application mobile"
+ }
+ },
+ "title": "Application mobile"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/.translations/it.json b/homeassistant/components/mobile_app/.translations/it.json
new file mode 100644
index 00000000000..8c083fad17e
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/it.json
@@ -0,0 +1,10 @@
+{
+ "config": {
+ "step": {
+ "confirm": {
+ "title": "App per dispositivi mobili"
+ }
+ },
+ "title": "App per dispositivi mobili"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/.translations/nn.json b/homeassistant/components/mobile_app/.translations/nn.json
new file mode 100644
index 00000000000..b4494a45ad2
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/nn.json
@@ -0,0 +1,10 @@
+{
+ "config": {
+ "step": {
+ "confirm": {
+ "title": "Mobilapp"
+ }
+ },
+ "title": "Mobilapp"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/.translations/pt.json b/homeassistant/components/mobile_app/.translations/pt.json
new file mode 100644
index 00000000000..1c61180726c
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/pt.json
@@ -0,0 +1,10 @@
+{
+ "config": {
+ "step": {
+ "confirm": {
+ "title": "Aplica\u00e7\u00e3o m\u00f3vel"
+ }
+ },
+ "title": "Aplica\u00e7\u00e3o m\u00f3vel"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/.translations/sv.json b/homeassistant/components/mobile_app/.translations/sv.json
new file mode 100644
index 00000000000..bdd94b84a1a
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/sv.json
@@ -0,0 +1,9 @@
+{
+ "config": {
+ "step": {
+ "confirm": {
+ "description": "Vill du st\u00e4lla in mobilappkomponenten?"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/.translations/zh-Hans.json b/homeassistant/components/mobile_app/.translations/zh-Hans.json
new file mode 100644
index 00000000000..b48ca1e4263
--- /dev/null
+++ b/homeassistant/components/mobile_app/.translations/zh-Hans.json
@@ -0,0 +1,12 @@
+{
+ "config": {
+ "abort": {
+ "install_app": "\u6253\u5f00\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u4ee5\u8bbe\u7f6e\u4e0e Home Assistant \u7684\u96c6\u6210\u3002\u8bf7\u53c2\u9605[\u6587\u6863]({apps_url})\u4ee5\u83b7\u53d6\u517c\u5bb9\u5e94\u7528\u7684\u5217\u8868\u3002"
+ },
+ "step": {
+ "confirm": {
+ "description": "\u60a8\u60f3\u8981\u914d\u7f6e\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u7ec4\u4ef6\u5417\uff1f"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py
index a4ae78959cf..711963a0b24 100644
--- a/homeassistant/components/mobile_app/__init__.py
+++ b/homeassistant/components/mobile_app/__init__.py
@@ -15,10 +15,6 @@ from .http_api import RegistrationsView
from .webhook import handle_webhook
from .websocket_api import register_websocket_handlers
-DEPENDENCIES = ['device_tracker', 'http', 'webhook']
-
-REQUIREMENTS = ['PyNaCl==1.3.0']
-
async def async_setup(hass: HomeAssistantType, config: ConfigType):
"""Set up the mobile app component."""
diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py
index 50943bb6504..71d9fd9d58a 100644
--- a/homeassistant/components/mobile_app/binary_sensor.py
+++ b/homeassistant/components/mobile_app/binary_sensor.py
@@ -13,8 +13,6 @@ from .const import (ATTR_SENSOR_STATE,
from .entity import MobileAppEntity, sensor_id
-DEPENDENCIES = ['mobile_app']
-
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up mobile app binary sensor from a config entry."""
diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py
index 05d240da909..8b33406216e 100644
--- a/homeassistant/components/mobile_app/const.py
+++ b/homeassistant/components/mobile_app/const.py
@@ -28,7 +28,6 @@ DATA_DEVICES = 'devices'
DATA_SENSOR = 'sensor'
DATA_STORE = 'store'
-ATTR_APP_COMPONENT = 'app_component'
ATTR_APP_DATA = 'app_data'
ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name'
@@ -66,7 +65,6 @@ ATTR_WEBHOOK_ENCRYPTED_DATA = 'encrypted_data'
ATTR_WEBHOOK_TYPE = 'type'
ERR_ENCRYPTION_REQUIRED = 'encryption_required'
-ERR_INVALID_COMPONENT = 'invalid_component'
ERR_SENSOR_NOT_REGISTERED = 'not_registered'
ERR_SENSOR_DUPLICATE_UNIQUE_ID = 'duplicate_unique_id'
@@ -89,7 +87,6 @@ WEBHOOK_TYPES = [WEBHOOK_TYPE_CALL_SERVICE, WEBHOOK_TYPE_FIRE_EVENT,
REGISTRATION_SCHEMA = vol.Schema({
- vol.Optional(ATTR_APP_COMPONENT): cv.string,
vol.Optional(ATTR_APP_DATA, default={}): dict,
vol.Required(ATTR_APP_ID): cv.string,
vol.Required(ATTR_APP_NAME): cv.string,
diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py
index 2ae8f441e52..8d63e797e0f 100644
--- a/homeassistant/components/mobile_app/http_api.py
+++ b/homeassistant/components/mobile_app/http_api.py
@@ -12,15 +12,11 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.const import (HTTP_CREATED, CONF_WEBHOOK_ID)
-from homeassistant.loader import get_component
+from .const import (ATTR_DEVICE_ID, ATTR_SUPPORTS_ENCRYPTION,
+ CONF_CLOUDHOOK_URL, CONF_REMOTE_UI_URL, CONF_SECRET,
+ CONF_USER_ID, DOMAIN, REGISTRATION_SCHEMA)
-from .const import (ATTR_APP_COMPONENT, ATTR_DEVICE_ID,
- ATTR_SUPPORTS_ENCRYPTION, CONF_CLOUDHOOK_URL,
- CONF_REMOTE_UI_URL, CONF_SECRET,
- CONF_USER_ID, DOMAIN, ERR_INVALID_COMPONENT,
- REGISTRATION_SCHEMA)
-
-from .helpers import error_response, supports_encryption
+from .helpers import supports_encryption
class RegistrationsView(HomeAssistantView):
@@ -34,20 +30,6 @@ class RegistrationsView(HomeAssistantView):
"""Handle the POST request for registration."""
hass = request.app['hass']
- if ATTR_APP_COMPONENT in data:
- component = get_component(hass, data[ATTR_APP_COMPONENT])
- if component is None:
- fmt_str = "{} is not a valid component."
- msg = fmt_str.format(data[ATTR_APP_COMPONENT])
- return error_response(ERR_INVALID_COMPONENT, msg)
-
- if (hasattr(component, 'DEPENDENCIES') is False or
- (hasattr(component, 'DEPENDENCIES') and
- DOMAIN not in component.DEPENDENCIES)):
- fmt_str = "{} is not compatible with mobile_app."
- msg = fmt_str.format(data[ATTR_APP_COMPONENT])
- return error_response(ERR_INVALID_COMPONENT, msg)
-
webhook_id = generate_secret()
if hass.components.cloud.async_active_subscription():
diff --git a/homeassistant/components/mobile_app/manifest.json b/homeassistant/components/mobile_app/manifest.json
new file mode 100644
index 00000000000..9c21858df1d
--- /dev/null
+++ b/homeassistant/components/mobile_app/manifest.json
@@ -0,0 +1,16 @@
+{
+ "domain": "mobile_app",
+ "name": "Home Assistant Mobile App Support",
+ "documentation": "https://www.home-assistant.io/components/mobile_app",
+ "requirements": [
+ "PyNaCl==1.3.0"
+ ],
+ "dependencies": [
+ "device_tracker",
+ "http",
+ "webhook"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py
index 8d2ac1b97ec..a69c020cfc8 100644
--- a/homeassistant/components/mobile_app/notify.py
+++ b/homeassistant/components/mobile_app/notify.py
@@ -22,8 +22,6 @@ from .const import (ATTR_APP_DATA, ATTR_APP_ID, ATTR_APP_VERSION,
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mobile_app']
-
def push_registrations(hass):
"""Return a dictionary of push enabled registrations."""
diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py
index 64ad69c5758..2e54c2f4f6c 100644
--- a/homeassistant/components/mobile_app/sensor.py
+++ b/homeassistant/components/mobile_app/sensor.py
@@ -12,8 +12,6 @@ from .const import (ATTR_SENSOR_STATE,
from .entity import MobileAppEntity, sensor_id
-DEPENDENCIES = ['mobile_app']
-
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up mobile app sensor from a config entry."""
diff --git a/homeassistant/components/mobile_app/services.yaml b/homeassistant/components/mobile_app/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/mochad/__init__.py b/homeassistant/components/mochad/__init__.py
index e10adf693fe..78d137c95ea 100644
--- a/homeassistant/components/mochad/__init__.py
+++ b/homeassistant/components/mochad/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.const import (CONF_HOST, CONF_PORT)
-REQUIREMENTS = ['pymochad==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
CONTROLLER = None
diff --git a/homeassistant/components/mochad/light.py b/homeassistant/components/mochad/light.py
index d2e1a567d27..4a734be4ebd 100644
--- a/homeassistant/components/mochad/light.py
+++ b/homeassistant/components/mochad/light.py
@@ -12,8 +12,6 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mochad']
-
CONF_BRIGHTNESS_LEVELS = 'brightness_levels'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/mochad/manifest.json b/homeassistant/components/mochad/manifest.json
new file mode 100644
index 00000000000..0e5c4dd1ff3
--- /dev/null
+++ b/homeassistant/components/mochad/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mochad",
+ "name": "Mochad",
+ "documentation": "https://www.home-assistant.io/components/mochad",
+ "requirements": [
+ "pymochad==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mochad/switch.py b/homeassistant/components/mochad/switch.py
index 03fd2db07bf..a4fb46130f3 100644
--- a/homeassistant/components/mochad/switch.py
+++ b/homeassistant/components/mochad/switch.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mochad']
-
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): mochad.DOMAIN,
diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py
index 0500a904cb9..7d882066260 100644
--- a/homeassistant/components/modbus/__init__.py
+++ b/homeassistant/components/modbus/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
CONF_TYPE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pymodbus==1.5.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py
index 0c10548452a..3a17f3c198d 100644
--- a/homeassistant/components/modbus/binary_sensor.py
+++ b/homeassistant/components/modbus/binary_sensor.py
@@ -15,8 +15,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_COIL = 'coil'
CONF_COILS = 'coils'
-DEPENDENCIES = ['modbus']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
vol.Required(CONF_COIL): cv.positive_int,
diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py
index 4d2b86903e7..cf7e2950923 100644
--- a/homeassistant/components/modbus/climate.py
+++ b/homeassistant/components/modbus/climate.py
@@ -22,8 +22,6 @@ CONF_PRECISION = 'precision'
DATA_TYPE_INT = 'int'
DATA_TYPE_UINT = 'uint'
DATA_TYPE_FLOAT = 'float'
-DEPENDENCIES = ['modbus']
-
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json
new file mode 100644
index 00000000000..e27f594b0af
--- /dev/null
+++ b/homeassistant/components/modbus/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "modbus",
+ "name": "Modbus",
+ "documentation": "https://www.home-assistant.io/components/modbus",
+ "requirements": [
+ "pymodbus==1.5.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py
index 10e11a9a656..bca5ef9d34d 100644
--- a/homeassistant/components/modbus/sensor.py
+++ b/homeassistant/components/modbus/sensor.py
@@ -29,8 +29,6 @@ DATA_TYPE_FLOAT = 'float'
DATA_TYPE_INT = 'int'
DATA_TYPE_UINT = 'uint'
-DEPENDENCIES = ['modbus']
-
REGISTER_TYPE_HOLDING = 'holding'
REGISTER_TYPE_INPUT = 'input'
diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py
index 69c5e3e4838..d74145ebad4 100644
--- a/homeassistant/components/modbus/switch.py
+++ b/homeassistant/components/modbus/switch.py
@@ -24,8 +24,6 @@ CONF_STATE_ON = 'state_on'
CONF_VERIFY_REGISTER = 'verify_register'
CONF_VERIFY_STATE = 'verify_state'
-DEPENDENCIES = ['modbus']
-
REGISTER_TYPE_HOLDING = 'holding'
REGISTER_TYPE_INPUT = 'input'
diff --git a/homeassistant/components/modem_callerid/manifest.json b/homeassistant/components/modem_callerid/manifest.json
new file mode 100644
index 00000000000..e3d6d19b803
--- /dev/null
+++ b/homeassistant/components/modem_callerid/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "modem_callerid",
+ "name": "Modem callerid",
+ "documentation": "https://www.home-assistant.io/components/modem_callerid",
+ "requirements": [
+ "basicmodem==0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py
index 2da7953c12a..0e1f02efecf 100644
--- a/homeassistant/components/modem_callerid/sensor.py
+++ b/homeassistant/components/modem_callerid/sensor.py
@@ -1,9 +1,4 @@
-"""
-A sensor to monitor incoming calls using a USB modem that supports caller ID.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.modem_callerid/
-"""
+"""A sensor for incoming calls using a USB modem that supports caller ID."""
import logging
import voluptuous as vol
from homeassistant.const import (STATE_IDLE,
@@ -14,8 +9,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['basicmodem==0.7']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Modem CallerID'
ICON = 'mdi:phone-classic'
diff --git a/homeassistant/components/mold_indicator/manifest.json b/homeassistant/components/mold_indicator/manifest.json
new file mode 100644
index 00000000000..de4680927a4
--- /dev/null
+++ b/homeassistant/components/mold_indicator/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "mold_indicator",
+ "name": "Mold indicator",
+ "documentation": "https://www.home-assistant.io/components/mold_indicator",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py
index 2a250f0e63d..645e04a890c 100644
--- a/homeassistant/components/mold_indicator/sensor.py
+++ b/homeassistant/components/mold_indicator/sensor.py
@@ -1,9 +1,4 @@
-"""
-Calculates mold growth indication from temperature and humidity.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mold_indicator/
-"""
+"""Calculates mold growth indication from temperature and humidity."""
import logging
import math
diff --git a/homeassistant/components/monoprice/manifest.json b/homeassistant/components/monoprice/manifest.json
new file mode 100644
index 00000000000..aa07911a697
--- /dev/null
+++ b/homeassistant/components/monoprice/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "monoprice",
+ "name": "Monoprice",
+ "documentation": "https://www.home-assistant.io/components/monoprice",
+ "requirements": [
+ "pymonoprice==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@etsinko"
+ ]
+}
diff --git a/homeassistant/components/monoprice/media_player.py b/homeassistant/components/monoprice/media_player.py
index e98ad47a6e7..d8f22a5d00b 100644
--- a/homeassistant/components/monoprice/media_player.py
+++ b/homeassistant/components/monoprice/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing with Monoprice 6 zone home audio controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.monoprice/
-"""
+"""Support for interfacing with Monoprice 6 zone home audio controller."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pymonoprice==0.3']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_MONOPRICE = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
diff --git a/homeassistant/components/monoprice/services.yaml b/homeassistant/components/monoprice/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/moon/.translations/sensor.es.json b/homeassistant/components/moon/.translations/sensor.es.json
index 3ce14cd4c77..b3456735754 100644
--- a/homeassistant/components/moon/.translations/sensor.es.json
+++ b/homeassistant/components/moon/.translations/sensor.es.json
@@ -1,10 +1,12 @@
{
"state": {
- "first_quarter": "Primer cuarto",
+ "first_quarter": "Cuarto creciente",
"full_moon": "Luna llena",
- "last_quarter": "\u00daltimo cuarto",
+ "last_quarter": "Cuarto menguante",
"new_moon": "Luna nueva",
- "waning_crescent": "Luna menguante",
- "waxing_crescent": "Luna creciente"
+ "waning_crescent": "Menguante",
+ "waning_gibbous": "Gibosa menguante",
+ "waxing_crescent": "Nueva visible",
+ "waxing_gibbous": "Gibosa creciente"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/moon/.translations/sensor.lb.json b/homeassistant/components/moon/.translations/sensor.lb.json
index d2f95685634..174d1fdcc13 100644
--- a/homeassistant/components/moon/.translations/sensor.lb.json
+++ b/homeassistant/components/moon/.translations/sensor.lb.json
@@ -1,6 +1,12 @@
{
"state": {
+ "first_quarter": "zouhuelend",
"full_moon": "Vollmound",
- "new_moon": "Neimound"
+ "last_quarter": "ofhuelend",
+ "new_moon": "Neimound",
+ "waning_crescent": "ofhuelend hallef",
+ "waning_gibbous": "ofhuelend dr\u00e4i v\u00e9ierels",
+ "waxing_crescent": "zouhuelend hallef",
+ "waxing_gibbous": "zouhuelend dr\u00e4i v\u00e9ierels"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/moon/.translations/sensor.pt.json b/homeassistant/components/moon/.translations/sensor.pt.json
index 14961ab98f0..c73ff5b2977 100644
--- a/homeassistant/components/moon/.translations/sensor.pt.json
+++ b/homeassistant/components/moon/.translations/sensor.pt.json
@@ -2,11 +2,6 @@
"state": {
"first_quarter": "Quarto crescente",
"full_moon": "Lua cheia",
- "last_quarter": "Quarto minguante",
- "new_moon": "Lua nova",
- "waning_crescent": "Lua crescente",
- "waning_gibbous": "Minguante convexa",
- "waxing_crescent": "Lua minguante",
- "waxing_gibbous": "Crescente convexa"
+ "new_moon": "Lua nova"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/moon/.translations/sensor.th.json b/homeassistant/components/moon/.translations/sensor.th.json
new file mode 100644
index 00000000000..5d65c23226d
--- /dev/null
+++ b/homeassistant/components/moon/.translations/sensor.th.json
@@ -0,0 +1,5 @@
+{
+ "state": {
+ "full_moon": "\u0e1e\u0e23\u0e30\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c\u0e40\u0e15\u0e47\u0e21\u0e14\u0e27\u0e07"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/moon/manifest.json b/homeassistant/components/moon/manifest.json
new file mode 100644
index 00000000000..50a93fce20a
--- /dev/null
+++ b/homeassistant/components/moon/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "moon",
+ "name": "Moon",
+ "documentation": "https://www.home-assistant.io/components/moon",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py
index a019d21fd78..3b1d70bc731 100644
--- a/homeassistant/components/moon/sensor.py
+++ b/homeassistant/components/moon/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for tracking the moon phases.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.moon/
-"""
+"""Support for tracking the moon phases."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/mopar/__init__.py b/homeassistant/components/mopar/__init__.py
index 4ee9f3219b4..ec723b94fcc 100644
--- a/homeassistant/components/mopar/__init__.py
+++ b/homeassistant/components/mopar/__init__.py
@@ -18,8 +18,6 @@ from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['motorparts==1.1.0']
-
DOMAIN = 'mopar'
DATA_UPDATED = '{}_data_updated'.format(DOMAIN)
diff --git a/homeassistant/components/mopar/lock.py b/homeassistant/components/mopar/lock.py
index aa2e0161813..5a41058bb53 100644
--- a/homeassistant/components/mopar/lock.py
+++ b/homeassistant/components/mopar/lock.py
@@ -7,8 +7,6 @@ from homeassistant.components.mopar import (
)
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
-DEPENDENCIES = ['mopar']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/mopar/manifest.json b/homeassistant/components/mopar/manifest.json
new file mode 100644
index 00000000000..5acd5bbdcdb
--- /dev/null
+++ b/homeassistant/components/mopar/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mopar",
+ "name": "Mopar",
+ "documentation": "https://www.home-assistant.io/components/mopar",
+ "requirements": [
+ "motorparts==1.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mopar/sensor.py b/homeassistant/components/mopar/sensor.py
index 0d6e5765fda..f09c0bdbea9 100644
--- a/homeassistant/components/mopar/sensor.py
+++ b/homeassistant/components/mopar/sensor.py
@@ -10,7 +10,6 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
-DEPENDENCIES = ['mopar']
ICON = 'mdi:car'
diff --git a/homeassistant/components/mopar/switch.py b/homeassistant/components/mopar/switch.py
index 352cdafbd41..4e1ff606100 100644
--- a/homeassistant/components/mopar/switch.py
+++ b/homeassistant/components/mopar/switch.py
@@ -5,8 +5,6 @@ from homeassistant.components.mopar import DOMAIN as MOPAR_DOMAIN
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_ON, STATE_OFF
-DEPENDENCIES = ['mopar']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/mpchc/manifest.json b/homeassistant/components/mpchc/manifest.json
new file mode 100644
index 00000000000..e874ca28891
--- /dev/null
+++ b/homeassistant/components/mpchc/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "mpchc",
+ "name": "Mpchc",
+ "documentation": "https://www.home-assistant.io/components/mpchc",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mpchc/media_player.py b/homeassistant/components/mpchc/media_player.py
index 61d89c6d0b1..54518667949 100644
--- a/homeassistant/components/mpchc/media_player.py
+++ b/homeassistant/components/mpchc/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support to interface with the MPC-HC Web API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.mpchc/
-"""
+"""Support to interface with the MPC-HC Web API."""
import logging
import re
diff --git a/homeassistant/components/mpd/manifest.json b/homeassistant/components/mpd/manifest.json
new file mode 100644
index 00000000000..beee3137ef5
--- /dev/null
+++ b/homeassistant/components/mpd/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "mpd",
+ "name": "Mpd",
+ "documentation": "https://www.home-assistant.io/components/mpd",
+ "requirements": [
+ "python-mpd2==1.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py
index 9d8015109b2..5340bc46b12 100644
--- a/homeassistant/components/mpd/media_player.py
+++ b/homeassistant/components/mpd/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support to interact with a Music Player Daemon.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.mpd/
-"""
+"""Support to interact with a Music Player Daemon."""
from datetime import timedelta
import logging
import os
@@ -25,8 +20,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['python-mpd2==1.0.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MPD'
diff --git a/homeassistant/components/mqtt/.translations/ko.json b/homeassistant/components/mqtt/.translations/ko.json
index ed552c0d994..e2a1ef6456e 100644
--- a/homeassistant/components/mqtt/.translations/ko.json
+++ b/homeassistant/components/mqtt/.translations/ko.json
@@ -22,7 +22,7 @@
"data": {
"discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654"
},
- "description": "Hass.io \uc560\ub4dc\uc628 {addon} \uc5d0\uc11c \uc81c\uacf5\ud558\ub294 MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "description": "Hass.io \uc560\ub4dc\uc628 {addon} \ub85c(\uc73c\ub85c) MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "Hass.io \uc560\ub4dc\uc628\uc758 MQTT \ube0c\ub85c\ucee4"
}
},
diff --git a/homeassistant/components/mqtt/.translations/ru.json b/homeassistant/components/mqtt/.translations/ru.json
index ad3a90383b1..aacac084b19 100644
--- a/homeassistant/components/mqtt/.translations/ru.json
+++ b/homeassistant/components/mqtt/.translations/ru.json
@@ -22,8 +22,8 @@
"data": {
"discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432"
},
- "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 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io {addon}?",
- "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io"
+ "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 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?",
+ "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/th.json b/homeassistant/components/mqtt/.translations/th.json
index 7ea8785af32..293b7e34314 100644
--- a/homeassistant/components/mqtt/.translations/th.json
+++ b/homeassistant/components/mqtt/.translations/th.json
@@ -3,7 +3,9 @@
"step": {
"broker": {
"data": {
- "discovery": "\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e01\u0e32\u0e23\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e2d\u0e38\u0e1b\u0e01\u0e23\u0e13\u0e4c"
+ "discovery": "\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e01\u0e32\u0e23\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e2d\u0e38\u0e1b\u0e01\u0e23\u0e13\u0e4c",
+ "password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19",
+ "username": "\u0e0a\u0e37\u0e48\u0e2d\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49"
}
},
"hassio_confirm": {
diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py
index 2c605fb4b0d..e226e966b09 100644
--- a/homeassistant/components/mqtt/__init__.py
+++ b/homeassistant/components/mqtt/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT message handling.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/mqtt/
-"""
+"""Support for MQTT message handling."""
import asyncio
from functools import partial, wraps
import inspect
@@ -28,23 +23,22 @@ from homeassistant.const import (
CONF_PROTOCOL, CONF_USERNAME, CONF_VALUE_TEMPLATE,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import Event, ServiceCall, callback
-from homeassistant.exceptions import HomeAssistantError, Unauthorized
+from homeassistant.exceptions import (
+ HomeAssistantError, Unauthorized, ConfigEntryNotReady)
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import (
ConfigType, HomeAssistantType, ServiceDataType)
from homeassistant.loader import bind_hass
-from homeassistant.setup import async_prepare_setup_platform
from homeassistant.util.async_ import (
run_callback_threadsafe, run_coroutine_threadsafe)
from homeassistant.util.logging import catch_log_exception
# Loading the config flow file will register the flow
-from . import config_flow # noqa pylint: disable=unused-import
-from .const import CONF_BROKER, CONF_DISCOVERY, DEFAULT_DISCOVERY
-from .server import HBMQTT_CONFIG_SCHEMA
-
-REQUIREMENTS = ['paho-mqtt==1.4.0']
+from . import config_flow, discovery, server # noqa pylint: disable=unused-import
+from .const import (
+ CONF_BROKER, CONF_DISCOVERY, DEFAULT_DISCOVERY, CONF_STATE_TOPIC,
+ ATTR_DISCOVERY_HASH)
_LOGGER = logging.getLogger(__name__)
@@ -70,7 +64,6 @@ CONF_TLS_VERSION = 'tls_version'
CONF_BIRTH_MESSAGE = 'birth_message'
CONF_WILL_MESSAGE = 'will_message'
-CONF_STATE_TOPIC = 'state_topic'
CONF_COMMAND_TOPIC = 'command_topic'
CONF_AVAILABILITY_TOPIC = 'availability_topic'
CONF_PAYLOAD_AVAILABLE = 'payload_available'
@@ -105,10 +98,13 @@ ATTR_PAYLOAD = 'payload'
ATTR_PAYLOAD_TEMPLATE = 'payload_template'
ATTR_QOS = CONF_QOS
ATTR_RETAIN = CONF_RETAIN
-ATTR_DISCOVERY_HASH = 'discovery_hash'
MAX_RECONNECT_WAIT = 300 # seconds
+CONNECTION_SUCCESS = 'connection_success'
+CONNECTION_FAILED = 'connection_failed'
+CONNECTION_FAILED_RECOVERABLE = 'connection_failed_recoverable'
+
def valid_topic(value: Any) -> str:
"""Validate that this is a valid topic name/filter."""
@@ -179,6 +175,16 @@ MQTT_WILL_BIRTH_SCHEMA = vol.Schema({
vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
}, required=True)
+
+def embedded_broker_deprecated(value):
+ """Warn user that embedded MQTT broker is deprecated."""
+ _LOGGER.warning(
+ "The embedded MQTT broker has been deprecated and will stop working"
+ "after June 5th, 2019. Use an external broker instead. For"
+ "instructions, see https://www.home-assistant.io/docs/mqtt/broker")
+ return value
+
+
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_CLIENT_ID): cv.string,
@@ -198,7 +204,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.Any('auto', '1.0', '1.1', '1.2'),
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL):
vol.All(cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])),
- vol.Optional(CONF_EMBEDDED): HBMQTT_CONFIG_SCHEMA,
+ vol.Optional(CONF_EMBEDDED):
+ vol.All(server.HBMQTT_CONFIG_SCHEMA, embedded_broker_deprecated),
vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean,
@@ -360,7 +367,7 @@ async def async_subscribe(hass: HomeAssistantType, topic: str,
wrapped_msg_callback = msg_callback
# If we have 3 paramaters with no default value, wrap the callback
if non_default == 3:
- _LOGGER.info(
+ _LOGGER.warning(
"Signature of MQTT msg_callback '%s.%s' is deprecated",
inspect.getmodule(msg_callback).__name__, msg_callback.__name__)
wrapped_msg_callback = wrap_msg_callback(msg_callback)
@@ -397,13 +404,6 @@ async def _async_setup_server(hass: HomeAssistantType, config: ConfigType):
"""
conf = config.get(DOMAIN, {}) # type: ConfigType
- server = await async_prepare_setup_platform(
- hass, config, DOMAIN, 'server')
-
- if server is None:
- _LOGGER.error("Unable to load embedded server")
- return None
-
success, broker_config = \
await server.async_start(
hass, conf.get(CONF_PASSWORD), conf.get(CONF_EMBEDDED))
@@ -421,9 +421,6 @@ async def _async_setup_discovery(hass: HomeAssistantType, conf: ConfigType,
This method is a coroutine.
"""
- discovery = await async_prepare_setup_platform(
- hass, hass_config, DOMAIN, 'discovery')
-
if discovery is None:
_LOGGER.error("Unable to load MQTT discovery")
return False
@@ -574,11 +571,14 @@ async def async_setup_entry(hass, entry):
tls_version=tls_version,
)
- success = await hass.data[DATA_MQTT].async_connect() # type: bool
+ result = await hass.data[DATA_MQTT].async_connect() # type: str
- if not success:
+ if result == CONNECTION_FAILED:
return False
+ if result == CONNECTION_FAILED_RECOVERABLE:
+ raise ConfigEntryNotReady
+
async def async_stop_mqtt(event: Event):
"""Stop MQTT component."""
await hass.data[DATA_MQTT].async_disconnect()
@@ -690,7 +690,7 @@ class MQTT:
await self.hass.async_add_job(
self._mqttc.publish, topic, payload, qos, retain)
- async def async_connect(self) -> bool:
+ async def async_connect(self) -> str:
"""Connect to the host. Does process messages yet.
This method is a coroutine.
@@ -701,15 +701,15 @@ class MQTT:
self._mqttc.connect, self.broker, self.port, self.keepalive)
except OSError as err:
_LOGGER.error("Failed to connect due to exception: %s", err)
- return False
+ return CONNECTION_FAILED_RECOVERABLE
if result != 0:
import paho.mqtt.client as mqtt
_LOGGER.error("Failed to connect: %s", mqtt.error_string(result))
- return False
+ return CONNECTION_FAILED
self._mqttc.loop_start()
- return True
+ return CONNECTION_SUCCESS
@callback
def async_disconnect(self):
diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py
index 9498b597590..da3e2faf224 100644
--- a/homeassistant/components/mqtt/alarm_control_panel.py
+++ b/homeassistant/components/mqtt/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-This platform enables the possibility to control a MQTT alarm.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.mqtt/
-"""
+"""This platform enables the possibility to control a MQTT alarm."""
import logging
import re
@@ -12,9 +7,9 @@ import voluptuous as vol
from homeassistant.components import mqtt
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (
- CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY,
- STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
- STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
+ CONF_CODE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE,
+ STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
+ STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -29,31 +24,36 @@ from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
CONF_CODE_ARM_REQUIRED = 'code_arm_required'
+CONF_CODE_DISARM_REQUIRED = 'code_disarm_required'
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_PAYLOAD_ARM_NIGHT = 'payload_arm_night'
+CONF_COMMAND_TEMPLATE = 'command_template'
+DEFAULT_COMMAND_TEMPLATE = '{{action}}'
DEFAULT_ARM_NIGHT = 'ARM_NIGHT'
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
DEFAULT_DISARM = 'DISARM'
DEFAULT_NAME = 'MQTT Alarm'
-DEPENDENCIES = ['mqtt']
-
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
- vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
- vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_CODE): cv.string,
+ vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
+ vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean,
+ vol.Optional(CONF_COMMAND_TEMPLATE,
+ default=DEFAULT_COMMAND_TEMPLATE): cv.template,
+ vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
+ vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
+ vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
+ vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
- vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
+ vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -125,10 +125,20 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
+ value_template = self._config.get(CONF_VALUE_TEMPLATE)
+ if value_template is not None:
+ value_template.hass = self.hass
+ command_template = self._config[CONF_COMMAND_TEMPLATE]
+ command_template.hass = self.hass
+
@callback
def message_received(msg):
"""Run when new MQTT message has been received."""
- if msg.payload not in (
+ payload = msg.payload
+ if value_template is not None:
+ payload = value_template.async_render_with_possible_json_value(
+ msg.payload, self._state)
+ if payload not in (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_NIGHT,
@@ -136,14 +146,14 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
STATE_ALARM_TRIGGERED):
_LOGGER.warning("Received unexpected payload: %s", msg.payload)
return
- self._state = msg.payload
+ self._state = payload
self.async_write_ha_state()
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
- {'state_topic': {'topic': self._config.get(CONF_STATE_TOPIC),
+ {'state_topic': {'topic': self._config[CONF_STATE_TOPIC],
'msg_callback': message_received,
- 'qos': self._config.get(CONF_QOS)}})
+ 'qos': self._config[CONF_QOS]}})
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
@@ -160,7 +170,7 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the device."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unique_id(self):
@@ -187,55 +197,55 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
This method is a coroutine.
"""
- if not self._validate_code(code, 'disarming'):
+ code_required = self._config[CONF_CODE_DISARM_REQUIRED]
+ if code_required and not self._validate_code(code, 'disarming'):
return
- mqtt.async_publish(
- self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_DISARM),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ payload = self._config[CONF_PAYLOAD_DISARM]
+ self._publish(code, payload)
async def async_alarm_arm_home(self, code=None):
"""Send arm home command.
This method is a coroutine.
"""
- code_required = self._config.get(CONF_CODE_ARM_REQUIRED)
+ code_required = self._config[CONF_CODE_ARM_REQUIRED]
if code_required and not self._validate_code(code, 'arming home'):
return
- mqtt.async_publish(
- self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_ARM_HOME),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ action = self._config[CONF_PAYLOAD_ARM_HOME]
+ self._publish(code, action)
async def async_alarm_arm_away(self, code=None):
"""Send arm away command.
This method is a coroutine.
"""
- code_required = self._config.get(CONF_CODE_ARM_REQUIRED)
+ code_required = self._config[CONF_CODE_ARM_REQUIRED]
if code_required and not self._validate_code(code, 'arming away'):
return
- mqtt.async_publish(
- self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_ARM_AWAY),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ action = self._config[CONF_PAYLOAD_ARM_AWAY]
+ self._publish(code, action)
async def async_alarm_arm_night(self, code=None):
"""Send arm night command.
This method is a coroutine.
"""
- code_required = self._config.get(CONF_CODE_ARM_REQUIRED)
+ code_required = self._config[CONF_CODE_ARM_REQUIRED]
if code_required and not self._validate_code(code, 'arming night'):
return
+ action = self._config[CONF_PAYLOAD_ARM_NIGHT]
+ self._publish(code, action)
+
+ def _publish(self, code, action):
+ """Publish via mqtt."""
+ command_template = self._config[CONF_COMMAND_TEMPLATE]
+ values = {'action': action, 'code': code}
+ payload = command_template.async_render(**values)
mqtt.async_publish(
- self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_ARM_NIGHT),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self.hass, self._config[CONF_COMMAND_TOPIC],
+ payload,
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
def _validate_code(self, code, state):
"""Validate given code."""
diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py
index 7532f305328..904a456fc46 100644
--- a/homeassistant/components/mqtt/binary_sensor.py
+++ b/homeassistant/components/mqtt/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT binary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.mqtt/
-"""
+"""Support for MQTT binary sensors."""
import logging
import voluptuous as vol
@@ -34,20 +29,16 @@ DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_FORCE_UPDATE = False
-DEPENDENCIES = ['mqtt']
-
PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
- vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OFF_DELAY):
vol.All(vol.Coerce(int), vol.Range(min=0)),
- # Integrations should never expose unique_id through configuration.
- # This is an exception because MQTT is a message transport, not a protocol
+ vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
+ vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -140,15 +131,15 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
payload, variables={'entity_id': self.entity_id})
- if payload == self._config.get(CONF_PAYLOAD_ON):
+ if payload == self._config[CONF_PAYLOAD_ON]:
self._state = True
- elif payload == self._config.get(CONF_PAYLOAD_OFF):
+ elif payload == self._config[CONF_PAYLOAD_OFF]:
self._state = False
else: # Payload is not for this entity
_LOGGER.warning('No matching payload found'
' for entity: %s with state_topic: %s',
- self._config.get(CONF_NAME),
- self._config.get(CONF_STATE_TOPIC))
+ self._config[CONF_NAME],
+ self._config[CONF_STATE_TOPIC])
return
if self._delay_listener is not None:
@@ -164,9 +155,9 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
- {'state_topic': {'topic': self._config.get(CONF_STATE_TOPIC),
+ {'state_topic': {'topic': self._config[CONF_STATE_TOPIC],
'msg_callback': state_message_received,
- 'qos': self._config.get(CONF_QOS)}})
+ 'qos': self._config[CONF_QOS]}})
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
@@ -183,7 +174,7 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the binary sensor."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def is_on(self):
@@ -198,7 +189,7 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def force_update(self):
"""Force update."""
- return self._config.get(CONF_FORCE_UPDATE)
+ return self._config[CONF_FORCE_UPDATE]
@property
def unique_id(self):
diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py
index 889e5533403..49679fc5583 100644
--- a/homeassistant/components/mqtt/camera.py
+++ b/homeassistant/components/mqtt/camera.py
@@ -1,9 +1,4 @@
-"""
-Camera that loads a picture from an MQTT topic.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.mqtt/
-"""
+"""Camera that loads a picture from an MQTT topic."""
import asyncio
import logging
@@ -19,8 +14,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import (
- ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_UNIQUE_ID, MqttDiscoveryUpdate,
- subscription)
+ ATTR_DISCOVERY_HASH, CONF_UNIQUE_ID, MqttDiscoveryUpdate, subscription)
from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
@@ -28,12 +22,10 @@ _LOGGER = logging.getLogger(__name__)
CONF_TOPIC = 'topic'
DEFAULT_NAME = 'MQTT Camera'
-DEPENDENCIES = ['mqtt']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
@@ -49,8 +41,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Discover and add a MQTT camera."""
try:
discovery_hash = discovery_payload.pop(ATTR_DISCOVERY_HASH)
- # state_topic is implicitly set by MQTT discovery, remove it
- discovery_payload.pop(CONF_STATE_TOPIC, None)
config = PLATFORM_SCHEMA(discovery_payload)
await _async_setup_entity(config, async_add_entities,
discovery_hash)
@@ -92,8 +82,6 @@ class MqttCamera(MqttDiscoveryUpdate, Camera):
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
- # state_topic is implicitly set by MQTT discovery, remove it
- discovery_payload.pop(CONF_STATE_TOPIC, None)
config = PLATFORM_SCHEMA(discovery_payload)
self._config = config
await self._subscribe_topics()
@@ -108,7 +96,7 @@ class MqttCamera(MqttDiscoveryUpdate, Camera):
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
- {'state_topic': {'topic': self._config.get(CONF_TOPIC),
+ {'state_topic': {'topic': self._config[CONF_TOPIC],
'msg_callback': message_received,
'qos': self._qos,
'encoding': None}})
@@ -126,7 +114,7 @@ class MqttCamera(MqttDiscoveryUpdate, Camera):
@property
def name(self):
"""Return the name of this camera."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unique_id(self):
diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py
index a9c23d27e11..e50aff8d209 100644
--- a/homeassistant/components/mqtt/climate.py
+++ b/homeassistant/components/mqtt/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT climate devices.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/climate.mqtt/
-"""
+"""Support for MQTT climate devices."""
import logging
import voluptuous as vol
@@ -15,7 +10,10 @@ from homeassistant.components.climate.const import (
ATTR_OPERATION_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, STATE_AUTO,
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT,
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE,
- SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE)
+ SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
+ ATTR_TARGET_TEMP_LOW,
+ ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
+ SUPPORT_TARGET_TEMPERATURE_HIGH)
from homeassistant.components.fan import SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_OFF,
@@ -26,123 +24,148 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import (
- ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
- CONF_UNIQUE_ID, MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes,
- MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
+ ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_UNIQUE_ID,
+ MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes, MqttAvailability,
+ MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
DEFAULT_NAME = 'MQTT HVAC'
-CONF_POWER_COMMAND_TOPIC = 'power_command_topic'
-CONF_POWER_STATE_TOPIC = 'power_state_topic'
-CONF_POWER_STATE_TEMPLATE = 'power_state_template'
-CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
-CONF_MODE_STATE_TOPIC = 'mode_state_topic'
-CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
-CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic'
-CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic'
-CONF_TEMPERATURE_STATE_TEMPLATE = 'temperature_state_template'
-CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic'
-CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
-CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template'
-CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic'
-CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic'
-CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template'
-CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic'
-CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic'
-CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template'
-CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
-CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
-CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
CONF_AUX_COMMAND_TOPIC = 'aux_command_topic'
-CONF_AUX_STATE_TOPIC = 'aux_state_topic'
CONF_AUX_STATE_TEMPLATE = 'aux_state_template'
-
-CONF_CURRENT_TEMPERATURE_TEMPLATE = 'current_temperature_template'
-CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic'
-
-CONF_PAYLOAD_ON = 'payload_on'
-CONF_PAYLOAD_OFF = 'payload_off'
-
+CONF_AUX_STATE_TOPIC = 'aux_state_topic'
+CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic'
+CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template'
+CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic'
+CONF_CURRENT_TEMP_TEMPLATE = 'current_temperature_template'
+CONF_CURRENT_TEMP_TOPIC = 'current_temperature_topic'
+CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic'
CONF_FAN_MODE_LIST = 'fan_modes'
+CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template'
+CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic'
+CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic'
+CONF_HOLD_STATE_TEMPLATE = 'hold_state_template'
+CONF_HOLD_STATE_TOPIC = 'hold_state_topic'
+CONF_MODE_COMMAND_TOPIC = 'mode_command_topic'
CONF_MODE_LIST = 'modes'
-CONF_SWING_MODE_LIST = 'swing_modes'
-CONF_INITIAL = 'initial'
+CONF_MODE_STATE_TEMPLATE = 'mode_state_template'
+CONF_MODE_STATE_TOPIC = 'mode_state_topic'
+CONF_PAYLOAD_OFF = 'payload_off'
+CONF_PAYLOAD_ON = 'payload_on'
+CONF_POWER_COMMAND_TOPIC = 'power_command_topic'
+CONF_POWER_STATE_TEMPLATE = 'power_state_template'
+CONF_POWER_STATE_TOPIC = 'power_state_topic'
CONF_SEND_IF_OFF = 'send_if_off'
-
-CONF_MIN_TEMP = 'min_temp'
-CONF_MAX_TEMP = 'max_temp'
+CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic'
+CONF_SWING_MODE_LIST = 'swing_modes'
+CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template'
+CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic'
+CONF_TEMP_COMMAND_TOPIC = 'temperature_command_topic'
+CONF_TEMP_HIGH_COMMAND_TOPIC = 'temperature_high_command_topic'
+CONF_TEMP_HIGH_STATE_TEMPLATE = 'temperature_high_state_template'
+CONF_TEMP_HIGH_STATE_TOPIC = 'temperature_high_state_topic'
+CONF_TEMP_LOW_COMMAND_TOPIC = 'temperature_low_command_topic'
+CONF_TEMP_LOW_STATE_TEMPLATE = 'temperature_low_state_template'
+CONF_TEMP_LOW_STATE_TOPIC = 'temperature_low_state_topic'
+CONF_TEMP_STATE_TEMPLATE = 'temperature_state_template'
+CONF_TEMP_STATE_TOPIC = 'temperature_state_topic'
+CONF_TEMP_INITIAL = 'initial'
+CONF_TEMP_MAX = 'max_temp'
+CONF_TEMP_MIN = 'min_temp'
CONF_TEMP_STEP = 'temp_step'
TEMPLATE_KEYS = (
- CONF_POWER_STATE_TEMPLATE,
- CONF_MODE_STATE_TEMPLATE,
- CONF_TEMPERATURE_STATE_TEMPLATE,
- CONF_FAN_MODE_STATE_TEMPLATE,
- CONF_SWING_MODE_STATE_TEMPLATE,
- CONF_AWAY_MODE_STATE_TEMPLATE,
- CONF_HOLD_STATE_TEMPLATE,
CONF_AUX_STATE_TEMPLATE,
- CONF_CURRENT_TEMPERATURE_TEMPLATE
+ CONF_AWAY_MODE_STATE_TEMPLATE,
+ CONF_CURRENT_TEMP_TEMPLATE,
+ CONF_FAN_MODE_STATE_TEMPLATE,
+ CONF_HOLD_STATE_TEMPLATE,
+ CONF_MODE_STATE_TEMPLATE,
+ CONF_POWER_STATE_TEMPLATE,
+ CONF_SWING_MODE_STATE_TEMPLATE,
+ CONF_TEMP_HIGH_STATE_TEMPLATE,
+ CONF_TEMP_LOW_STATE_TEMPLATE,
+ CONF_TEMP_STATE_TEMPLATE,
+)
+
+TOPIC_KEYS = (
+ CONF_AUX_COMMAND_TOPIC,
+ CONF_AUX_STATE_TOPIC,
+ CONF_AWAY_MODE_COMMAND_TOPIC,
+ CONF_AWAY_MODE_STATE_TOPIC,
+ CONF_CURRENT_TEMP_TOPIC,
+ CONF_FAN_MODE_COMMAND_TOPIC,
+ CONF_FAN_MODE_STATE_TOPIC,
+ CONF_HOLD_COMMAND_TOPIC,
+ CONF_HOLD_STATE_TOPIC,
+ CONF_MODE_COMMAND_TOPIC,
+ CONF_MODE_STATE_TOPIC,
+ CONF_POWER_COMMAND_TOPIC,
+ CONF_POWER_STATE_TOPIC,
+ CONF_SWING_MODE_COMMAND_TOPIC,
+ CONF_SWING_MODE_STATE_TOPIC,
+ CONF_TEMP_COMMAND_TOPIC,
+ CONF_TEMP_HIGH_COMMAND_TOPIC,
+ CONF_TEMP_HIGH_STATE_TOPIC,
+ CONF_TEMP_LOW_COMMAND_TOPIC,
+ CONF_TEMP_LOW_STATE_TOPIC,
+ CONF_TEMP_STATE_TOPIC,
)
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
- vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
- vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic,
-
- vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic,
-
- vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
- vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template,
-
- vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC):
- mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template,
+ vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_FAN_MODE_LIST,
default=[STATE_AUTO, SPEED_LOW,
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
- vol.Optional(CONF_SWING_MODE_LIST,
- default=[STATE_ON, STATE_OFF]): cv.ensure_list,
+ vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_MODE_LIST,
default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT,
STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list,
+ vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_INITIAL, default=21): cv.positive_int,
- vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
-
- vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
- vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
+ vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
+ vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
+ vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_SWING_MODE_LIST,
+ default=[STATE_ON, STATE_OFF]): cv.ensure_list,
+ vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int,
+ vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
+ vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float),
+ vol.Optional(CONF_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_TEMP_HIGH_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_TEMP_HIGH_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_TEMP_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_TEMP_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template,
+ vol.Optional(CONF_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -159,8 +182,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Discover and add a MQTT climate device."""
try:
discovery_hash = discovery_payload.pop(ATTR_DISCOVERY_HASH)
- # state_topic is implicitly set by MQTT discovery, remove it
- discovery_payload.pop(CONF_STATE_TOPIC, None)
config = PLATFORM_SCHEMA(discovery_payload)
await _async_setup_entity(hass, config, async_add_entities,
config_entry, discovery_hash)
@@ -192,17 +213,19 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._sub_state = None
self.hass = hass
- self._topic = None
- self._value_templates = None
- self._target_temperature = None
+ self._aux = False
+ self._away = False
self._current_fan_mode = None
self._current_operation = None
self._current_swing_mode = None
- self._unit_of_measurement = hass.config.units.temperature_unit
- self._away = False
+ self._current_temp = None
self._hold = None
- self._current_temperature = None
- self._aux = False
+ self._target_temp = None
+ self._target_temp_high = None
+ self._target_temp_low = None
+ self._topic = None
+ self._unit_of_measurement = hass.config.units.temperature_unit
+ self._value_templates = None
self._setup_from_config(config)
@@ -221,8 +244,6 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
- # state_topic is implicitly set by MQTT discovery, remove it
- discovery_payload.pop(CONF_STATE_TOPIC, None)
config = PLATFORM_SCHEMA(discovery_payload)
self._config = config
self._setup_from_config(config)
@@ -235,32 +256,22 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def _setup_from_config(self, config):
"""(Re)Setup the entity."""
self._topic = {
- key: config.get(key) for key in (
- CONF_POWER_COMMAND_TOPIC,
- CONF_MODE_COMMAND_TOPIC,
- CONF_TEMPERATURE_COMMAND_TOPIC,
- CONF_FAN_MODE_COMMAND_TOPIC,
- CONF_SWING_MODE_COMMAND_TOPIC,
- CONF_AWAY_MODE_COMMAND_TOPIC,
- CONF_HOLD_COMMAND_TOPIC,
- CONF_AUX_COMMAND_TOPIC,
- CONF_POWER_STATE_TOPIC,
- CONF_MODE_STATE_TOPIC,
- CONF_TEMPERATURE_STATE_TOPIC,
- CONF_FAN_MODE_STATE_TOPIC,
- CONF_SWING_MODE_STATE_TOPIC,
- CONF_AWAY_MODE_STATE_TOPIC,
- CONF_HOLD_STATE_TOPIC,
- CONF_AUX_STATE_TOPIC,
- CONF_CURRENT_TEMPERATURE_TOPIC
- )
+ key: config.get(key) for key in TOPIC_KEYS
}
# set to None in non-optimistic mode
- self._target_temperature = self._current_fan_mode = \
+ self._target_temp = self._current_fan_mode = \
self._current_operation = self._current_swing_mode = None
- if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
- self._target_temperature = config.get(CONF_INITIAL)
+ self._target_temp_low = None
+ self._target_temp_high = None
+
+ if self._topic[CONF_TEMP_STATE_TOPIC] is None:
+ self._target_temp = config[CONF_TEMP_INITIAL]
+ if self._topic[CONF_TEMP_LOW_STATE_TOPIC] is None:
+ self._target_temp_low = config[CONF_TEMP_INITIAL]
+ if self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is None:
+ self._target_temp_high = config[CONF_TEMP_INITIAL]
+
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = SPEED_LOW
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
@@ -272,199 +283,170 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._aux = False
value_templates = {}
+ for key in TEMPLATE_KEYS:
+ value_templates[key] = lambda value: value
if CONF_VALUE_TEMPLATE in config:
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = self.hass
- value_templates = {key: value_template for key in TEMPLATE_KEYS}
+ value_templates = {
+ key: value_template.async_render_with_possible_json_value
+ for key in TEMPLATE_KEYS}
for key in TEMPLATE_KEYS & config.keys():
- value_templates[key] = config.get(key)
- value_templates[key].hass = self.hass
+ tpl = config[key]
+ value_templates[key] = tpl.async_render_with_possible_json_value
+ tpl.hass = self.hass
self._value_templates = value_templates
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
topics = {}
- qos = self._config.get(CONF_QOS)
+ qos = self._config[CONF_QOS]
+
+ def add_subscription(topics, topic, msg_callback):
+ if self._topic[topic] is not None:
+ topics[topic] = {
+ 'topic': self._topic[topic],
+ 'msg_callback': msg_callback,
+ 'qos': qos}
+
+ def render_template(msg, template_name):
+ template = self._value_templates[template_name]
+ return template(msg.payload)
@callback
- def handle_current_temp_received(msg):
+ def handle_temperature_received(msg, template_name, attr):
+ """Handle temperature coming via MQTT."""
+ payload = render_template(msg, template_name)
+
+ try:
+ setattr(self, attr, float(payload))
+ self.async_write_ha_state()
+ except ValueError:
+ _LOGGER.error("Could not parse temperature from %s", payload)
+
+ @callback
+ def handle_current_temperature_received(msg):
"""Handle current temperature coming via MQTT."""
- payload = msg.payload
- if CONF_CURRENT_TEMPERATURE_TEMPLATE in self._value_templates:
- payload =\
- self._value_templates[CONF_CURRENT_TEMPERATURE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
+ handle_temperature_received(
+ msg, CONF_CURRENT_TEMP_TEMPLATE, '_current_temp')
- try:
- self._current_temperature = float(payload)
- self.async_write_ha_state()
- except ValueError:
- _LOGGER.error("Could not parse temperature from %s", payload)
-
- if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None:
- topics[CONF_CURRENT_TEMPERATURE_TOPIC] = {
- 'topic': self._topic[CONF_CURRENT_TEMPERATURE_TOPIC],
- 'msg_callback': handle_current_temp_received,
- 'qos': qos}
+ add_subscription(topics, CONF_CURRENT_TEMP_TOPIC,
+ handle_current_temperature_received)
@callback
- def handle_mode_received(msg):
- """Handle receiving mode via MQTT."""
- payload = msg.payload
- if CONF_MODE_STATE_TEMPLATE in self._value_templates:
- payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
-
- if payload not in self._config.get(CONF_MODE_LIST):
- _LOGGER.error("Invalid mode: %s", payload)
- else:
- self._current_operation = payload
- self.async_write_ha_state()
-
- if self._topic[CONF_MODE_STATE_TOPIC] is not None:
- topics[CONF_MODE_STATE_TOPIC] = {
- 'topic': self._topic[CONF_MODE_STATE_TOPIC],
- 'msg_callback': handle_mode_received,
- 'qos': qos}
-
- @callback
- def handle_temperature_received(msg):
+ def handle_target_temperature_received(msg):
"""Handle target temperature coming via MQTT."""
- payload = msg.payload
- if CONF_TEMPERATURE_STATE_TEMPLATE in self._value_templates:
- payload = \
- self._value_templates[CONF_TEMPERATURE_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
+ handle_temperature_received(
+ msg, CONF_TEMP_STATE_TEMPLATE, '_target_temp')
- try:
- self._target_temperature = float(payload)
+ add_subscription(topics, CONF_TEMP_STATE_TOPIC,
+ handle_target_temperature_received)
+
+ @callback
+ def handle_temperature_low_received(msg):
+ """Handle target temperature low coming via MQTT."""
+ handle_temperature_received(
+ msg, CONF_TEMP_LOW_STATE_TEMPLATE, '_target_temp_low')
+
+ add_subscription(topics, CONF_TEMP_LOW_STATE_TOPIC,
+ handle_temperature_low_received)
+
+ @callback
+ def handle_temperature_high_received(msg):
+ """Handle target temperature high coming via MQTT."""
+ handle_temperature_received(
+ msg, CONF_TEMP_HIGH_STATE_TEMPLATE, '_target_temp_high')
+
+ add_subscription(topics, CONF_TEMP_HIGH_STATE_TOPIC,
+ handle_temperature_high_received)
+
+ @callback
+ def handle_mode_received(msg, template_name, attr, mode_list):
+ """Handle receiving listed mode via MQTT."""
+ payload = render_template(msg, template_name)
+
+ if payload not in self._config[mode_list]:
+ _LOGGER.error("Invalid %s mode: %s", mode_list, payload)
+ else:
+ setattr(self, attr, payload)
self.async_write_ha_state()
- except ValueError:
- _LOGGER.error("Could not parse temperature from %s", payload)
- if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None:
- topics[CONF_TEMPERATURE_STATE_TOPIC] = {
- 'topic': self._topic[CONF_TEMPERATURE_STATE_TOPIC],
- 'msg_callback': handle_temperature_received,
- 'qos': qos}
+ @callback
+ def handle_current_mode_received(msg):
+ """Handle receiving mode via MQTT."""
+ handle_mode_received(msg, CONF_MODE_STATE_TEMPLATE,
+ '_current_operation', CONF_MODE_LIST)
+
+ add_subscription(topics, CONF_MODE_STATE_TOPIC,
+ handle_current_mode_received)
@callback
def handle_fan_mode_received(msg):
"""Handle receiving fan mode via MQTT."""
- payload = msg.payload
- if CONF_FAN_MODE_STATE_TEMPLATE in self._value_templates:
- payload = \
- self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
+ handle_mode_received(msg, CONF_FAN_MODE_STATE_TEMPLATE,
+ '_current_fan_mode', CONF_FAN_MODE_LIST)
- if payload not in self._config.get(CONF_FAN_MODE_LIST):
- _LOGGER.error("Invalid fan mode: %s", payload)
- else:
- self._current_fan_mode = payload
- self.async_write_ha_state()
-
- if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None:
- topics[CONF_FAN_MODE_STATE_TOPIC] = {
- 'topic': self._topic[CONF_FAN_MODE_STATE_TOPIC],
- 'msg_callback': handle_fan_mode_received,
- 'qos': qos}
+ add_subscription(topics, CONF_FAN_MODE_STATE_TOPIC,
+ handle_fan_mode_received)
@callback
def handle_swing_mode_received(msg):
"""Handle receiving swing mode via MQTT."""
- payload = msg.payload
- if CONF_SWING_MODE_STATE_TEMPLATE in self._value_templates:
- payload = \
- self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
+ handle_mode_received(msg, CONF_SWING_MODE_STATE_TEMPLATE,
+ '_current_swing_mode', CONF_SWING_MODE_LIST)
- if payload not in self._config.get(CONF_SWING_MODE_LIST):
- _LOGGER.error("Invalid swing mode: %s", payload)
+ add_subscription(topics, CONF_SWING_MODE_STATE_TOPIC,
+ handle_swing_mode_received)
+
+ @callback
+ def handle_onoff_mode_received(msg, template_name, attr):
+ """Handle receiving on/off mode via MQTT."""
+ payload = render_template(msg, template_name)
+ payload_on = self._config[CONF_PAYLOAD_ON]
+ payload_off = self._config[CONF_PAYLOAD_OFF]
+
+ if payload == "True":
+ payload = payload_on
+ elif payload == "False":
+ payload = payload_off
+
+ if payload == payload_on:
+ setattr(self, attr, True)
+ elif payload == payload_off:
+ setattr(self, attr, False)
else:
- self._current_swing_mode = payload
- self.async_write_ha_state()
+ _LOGGER.error("Invalid %s mode: %s", attr, payload)
- if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None:
- topics[CONF_SWING_MODE_STATE_TOPIC] = {
- 'topic': self._topic[CONF_SWING_MODE_STATE_TOPIC],
- 'msg_callback': handle_swing_mode_received,
- 'qos': qos}
+ self.async_write_ha_state()
@callback
def handle_away_mode_received(msg):
"""Handle receiving away mode via MQTT."""
- payload = msg.payload
- payload_on = self._config.get(CONF_PAYLOAD_ON)
- payload_off = self._config.get(CONF_PAYLOAD_OFF)
- if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates:
- payload = \
- self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
- if payload == "True":
- payload = payload_on
- elif payload == "False":
- payload = payload_off
+ handle_onoff_mode_received(
+ msg, CONF_AWAY_MODE_STATE_TEMPLATE, '_away')
- if payload == payload_on:
- self._away = True
- elif payload == payload_off:
- self._away = False
- else:
- _LOGGER.error("Invalid away mode: %s", payload)
-
- self.async_write_ha_state()
-
- if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
- topics[CONF_AWAY_MODE_STATE_TOPIC] = {
- 'topic': self._topic[CONF_AWAY_MODE_STATE_TOPIC],
- 'msg_callback': handle_away_mode_received,
- 'qos': qos}
+ add_subscription(topics, CONF_AWAY_MODE_STATE_TOPIC,
+ handle_away_mode_received)
@callback
def handle_aux_mode_received(msg):
"""Handle receiving aux mode via MQTT."""
- payload = msg.payload
- payload_on = self._config.get(CONF_PAYLOAD_ON)
- payload_off = self._config.get(CONF_PAYLOAD_OFF)
- if CONF_AUX_STATE_TEMPLATE in self._value_templates:
- payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
- if payload == "True":
- payload = payload_on
- elif payload == "False":
- payload = payload_off
+ handle_onoff_mode_received(
+ msg, CONF_AUX_STATE_TEMPLATE, '_aux')
- if payload == payload_on:
- self._aux = True
- elif payload == payload_off:
- self._aux = False
- else:
- _LOGGER.error("Invalid aux mode: %s", payload)
-
- self.async_write_ha_state()
-
- if self._topic[CONF_AUX_STATE_TOPIC] is not None:
- topics[CONF_AUX_STATE_TOPIC] = {
- 'topic': self._topic[CONF_AUX_STATE_TOPIC],
- 'msg_callback': handle_aux_mode_received,
- 'qos': qos}
+ add_subscription(topics, CONF_AUX_STATE_TOPIC,
+ handle_aux_mode_received)
@callback
def handle_hold_mode_received(msg):
"""Handle receiving hold mode via MQTT."""
- payload = msg.payload
- if CONF_HOLD_STATE_TEMPLATE in self._value_templates:
- payload = self._value_templates[CONF_HOLD_STATE_TEMPLATE].\
- async_render_with_possible_json_value(payload)
+ payload = render_template(msg, CONF_HOLD_STATE_TEMPLATE)
self._hold = payload
self.async_write_ha_state()
- if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
- topics[CONF_HOLD_STATE_TOPIC] = {
- 'topic': self._topic[CONF_HOLD_STATE_TOPIC],
- 'msg_callback': handle_hold_mode_received,
- 'qos': qos}
+ add_subscription(topics, CONF_HOLD_STATE_TOPIC,
+ handle_hold_mode_received)
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
@@ -485,7 +467,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the climate device."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unique_id(self):
@@ -500,12 +482,22 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def current_temperature(self):
"""Return the current temperature."""
- return self._current_temperature
+ return self._current_temp
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
- return self._target_temperature
+ return self._target_temp
+
+ @property
+ def target_temperature_low(self):
+ """Return the low target temperature we try to reach."""
+ return self._target_temp_low
+
+ @property
+ def target_temperature_high(self):
+ """Return the high target temperature we try to reach."""
+ return self._target_temp_high
@property
def current_operation(self):
@@ -515,12 +507,12 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def operation_list(self):
"""Return the list of available operation modes."""
- return self._config.get(CONF_MODE_LIST)
+ return self._config[CONF_MODE_LIST]
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
- return self._config.get(CONF_TEMP_STEP)
+ return self._config[CONF_TEMP_STEP]
@property
def is_away_mode_on(self):
@@ -545,7 +537,23 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def fan_list(self):
"""Return the list of available fan modes."""
- return self._config.get(CONF_FAN_MODE_LIST)
+ return self._config[CONF_FAN_MODE_LIST]
+
+ def _publish(self, topic, payload):
+ if self._topic[topic] is not None:
+ mqtt.async_publish(
+ self.hass, self._topic[topic], payload,
+ self._config[CONF_QOS], self._config[CONF_RETAIN])
+
+ def _set_temperature(self, temp, cmnd_topic, state_topic, attr):
+ if temp is not None:
+ if self._topic[state_topic] is None:
+ # optimistic mode
+ setattr(self, attr, temp)
+
+ if (self._config[CONF_SEND_IF_OFF] or
+ self._current_operation != STATE_OFF):
+ self._publish(cmnd_topic, temp)
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
@@ -553,29 +561,27 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
await self.async_set_operation_mode(operation_mode)
- if kwargs.get(ATTR_TEMPERATURE) is not None:
- if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
- # optimistic mode
- self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
+ self._set_temperature(
+ kwargs.get(ATTR_TEMPERATURE), CONF_TEMP_COMMAND_TOPIC,
+ CONF_TEMP_STATE_TOPIC, '_target_temp')
- if (self._config.get(CONF_SEND_IF_OFF) or
- self._current_operation != STATE_OFF):
- mqtt.async_publish(
- self.hass, self._topic[CONF_TEMPERATURE_COMMAND_TOPIC],
- kwargs.get(ATTR_TEMPERATURE), self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._set_temperature(
+ kwargs.get(ATTR_TARGET_TEMP_LOW), CONF_TEMP_LOW_COMMAND_TOPIC,
+ CONF_TEMP_LOW_STATE_TOPIC, '_target_temp_low')
+
+ self._set_temperature(
+ kwargs.get(ATTR_TARGET_TEMP_HIGH), CONF_TEMP_HIGH_COMMAND_TOPIC,
+ CONF_TEMP_HIGH_STATE_TOPIC, '_target_temp_high')
# Always optimistic?
self.async_write_ha_state()
async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
- if (self._config.get(CONF_SEND_IF_OFF) or
+ if (self._config[CONF_SEND_IF_OFF] or
self._current_operation != STATE_OFF):
- mqtt.async_publish(
- self.hass, self._topic[CONF_SWING_MODE_COMMAND_TOPIC],
- swing_mode, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._publish(CONF_SWING_MODE_COMMAND_TOPIC,
+ swing_mode)
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
self._current_swing_mode = swing_mode
@@ -583,12 +589,10 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def async_set_fan_mode(self, fan_mode):
"""Set new target temperature."""
- if (self._config.get(CONF_SEND_IF_OFF) or
+ if (self._config[CONF_SEND_IF_OFF] or
self._current_operation != STATE_OFF):
- mqtt.async_publish(
- self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC],
- fan_mode, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._publish(CONF_FAN_MODE_COMMAND_TOPIC,
+ fan_mode)
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = fan_mode
@@ -596,24 +600,17 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def async_set_operation_mode(self, operation_mode) -> None:
"""Set new operation mode."""
- qos = self._config.get(CONF_QOS)
- retain = self._config.get(CONF_RETAIN)
- if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
- if (self._current_operation == STATE_OFF and
- operation_mode != STATE_OFF):
- mqtt.async_publish(
- self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
- self._config.get(CONF_PAYLOAD_ON), qos, retain)
- elif (self._current_operation != STATE_OFF and
- operation_mode == STATE_OFF):
- mqtt.async_publish(
- self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
- self._config.get(CONF_PAYLOAD_OFF), qos, retain)
+ if (self._current_operation == STATE_OFF and
+ operation_mode != STATE_OFF):
+ self._publish(CONF_POWER_COMMAND_TOPIC,
+ self._config[CONF_PAYLOAD_ON])
+ elif (self._current_operation != STATE_OFF and
+ operation_mode == STATE_OFF):
+ self._publish(CONF_POWER_COMMAND_TOPIC,
+ self._config[CONF_PAYLOAD_OFF])
- if self._topic[CONF_MODE_COMMAND_TOPIC] is not None:
- mqtt.async_publish(
- self.hass, self._topic[CONF_MODE_COMMAND_TOPIC],
- operation_mode, qos, retain)
+ self._publish(CONF_MODE_COMMAND_TOPIC,
+ operation_mode)
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = operation_mode
@@ -627,79 +624,67 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def swing_list(self):
"""List of available swing modes."""
- return self._config.get(CONF_SWING_MODE_LIST)
+ return self._config[CONF_SWING_MODE_LIST]
+
+ def _set_away_mode(self, state):
+ self._publish(CONF_AWAY_MODE_COMMAND_TOPIC,
+ self._config[CONF_PAYLOAD_ON] if state
+ else self._config[CONF_PAYLOAD_OFF])
+
+ if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
+ self._away = state
+ self.async_write_ha_state()
async def async_turn_away_mode_on(self):
"""Turn away mode on."""
- if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
- mqtt.async_publish(self.hass,
- self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
- self._config.get(CONF_PAYLOAD_ON),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
-
- if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
- self._away = True
- self.async_write_ha_state()
+ self._set_away_mode(True)
async def async_turn_away_mode_off(self):
"""Turn away mode off."""
- if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
- mqtt.async_publish(self.hass,
- self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
- self._config.get(CONF_PAYLOAD_OFF),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
-
- if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
- self._away = False
- self.async_write_ha_state()
+ self._set_away_mode(False)
async def async_set_hold_mode(self, hold_mode):
"""Update hold mode on."""
- if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
- mqtt.async_publish(self.hass,
- self._topic[CONF_HOLD_COMMAND_TOPIC],
- hold_mode, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode)
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
self._hold = hold_mode
self.async_write_ha_state()
- async def async_turn_aux_heat_on(self):
- """Turn auxiliary heater on."""
- if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
- mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
- self._config.get(CONF_PAYLOAD_ON),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ def _set_aux_heat(self, state):
+ self._publish(CONF_AUX_COMMAND_TOPIC,
+ self._config[CONF_PAYLOAD_ON] if state
+ else self._config[CONF_PAYLOAD_OFF])
if self._topic[CONF_AUX_STATE_TOPIC] is None:
- self._aux = True
+ self._aux = state
self.async_write_ha_state()
+ async def async_turn_aux_heat_on(self):
+ """Turn auxiliary heater on."""
+ self._set_aux_heat(True)
+
async def async_turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
- if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
- mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
- self._config.get(CONF_PAYLOAD_OFF),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
-
- if self._topic[CONF_AUX_STATE_TOPIC] is None:
- self._aux = False
- self.async_write_ha_state()
+ self._set_aux_heat(False)
@property
def supported_features(self):
"""Return the list of supported features."""
support = 0
- if (self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None) or \
- (self._topic[CONF_TEMPERATURE_COMMAND_TOPIC] is not None):
+ if (self._topic[CONF_TEMP_STATE_TOPIC] is not None) or \
+ (self._topic[CONF_TEMP_COMMAND_TOPIC] is not None):
support |= SUPPORT_TARGET_TEMPERATURE
+ if (self._topic[CONF_TEMP_LOW_STATE_TOPIC] is not None) or \
+ (self._topic[CONF_TEMP_LOW_COMMAND_TOPIC] is not None):
+ support |= SUPPORT_TARGET_TEMPERATURE_LOW
+
+ if (self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is not None) or \
+ (self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None):
+ support |= SUPPORT_TARGET_TEMPERATURE_HIGH
+
if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \
(self._topic[CONF_MODE_STATE_TOPIC] is not None):
support |= SUPPORT_OPERATION_MODE
@@ -729,9 +714,9 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def min_temp(self):
"""Return the minimum temperature."""
- return self._config.get(CONF_MIN_TEMP)
+ return self._config[CONF_TEMP_MIN]
@property
def max_temp(self):
"""Return the maximum temperature."""
- return self._config.get(CONF_MAX_TEMP)
+ return self._config[CONF_TEMP_MAX]
diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py
index 3c22001f91c..42b1a5b6755 100644
--- a/homeassistant/components/mqtt/const.py
+++ b/homeassistant/components/mqtt/const.py
@@ -2,3 +2,6 @@
CONF_BROKER = 'broker'
CONF_DISCOVERY = 'discovery'
DEFAULT_DISCOVERY = False
+
+ATTR_DISCOVERY_HASH = 'discovery_hash'
+CONF_STATE_TOPIC = 'state_topic'
diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py
index 8116421ac10..e1ad21564b5 100644
--- a/homeassistant/components/mqtt/cover.py
+++ b/homeassistant/components/mqtt/cover.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT cover devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/cover.mqtt/
-"""
+"""Support for MQTT cover devices."""
import logging
import voluptuous as vol
@@ -30,8 +25,6 @@ from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
CONF_GET_POSITION_TOPIC = 'position_topic'
CONF_SET_POSITION_TEMPLATE = 'set_position_template'
CONF_SET_POSITION_TOPIC = 'set_position_topic'
@@ -89,38 +82,36 @@ def validate_options(value):
PLATFORM_SCHEMA = vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_SET_POSITION_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template,
- vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
- vol.Optional(CONF_GET_POSITION_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string,
- vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string,
- vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
- vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
- vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
- vol.Optional(CONF_POSITION_OPEN,
- default=DEFAULT_POSITION_OPEN): int,
- vol.Optional(CONF_POSITION_CLOSED,
- default=DEFAULT_POSITION_CLOSED): int,
- vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
- vol.Optional(CONF_TILT_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_TILT_STATUS_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_TILT_CLOSED_POSITION,
- default=DEFAULT_TILT_CLOSED_POSITION): int,
- vol.Optional(CONF_TILT_OPEN_POSITION,
- default=DEFAULT_TILT_OPEN_POSITION): int,
- vol.Optional(CONF_TILT_MIN, default=DEFAULT_TILT_MIN): int,
- vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int,
- vol.Optional(CONF_TILT_STATE_OPTIMISTIC,
- default=DEFAULT_TILT_OPTIMISTIC): cv.boolean,
- vol.Optional(CONF_TILT_INVERT_STATE,
- default=DEFAULT_TILT_INVERT_STATE): cv.boolean,
- vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
+ vol.Optional(CONF_GET_POSITION_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
+ vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string,
+ vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string,
+ vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
+ vol.Optional(CONF_POSITION_CLOSED, default=DEFAULT_POSITION_CLOSED): int,
+ vol.Optional(CONF_POSITION_OPEN, default=DEFAULT_POSITION_OPEN): int,
+ vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
+ vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template,
+ vol.Optional(CONF_SET_POSITION_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
+ vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
+ vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_TILT_CLOSED_POSITION,
+ default=DEFAULT_TILT_CLOSED_POSITION): int,
+ vol.Optional(CONF_TILT_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_TILT_INVERT_STATE,
+ default=DEFAULT_TILT_INVERT_STATE): cv.boolean,
+ vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int,
+ vol.Optional(CONF_TILT_MIN, default=DEFAULT_TILT_MIN): int,
+ vol.Optional(CONF_TILT_OPEN_POSITION,
+ default=DEFAULT_TILT_OPEN_POSITION): int,
+ vol.Optional(CONF_TILT_STATE_OPTIMISTIC,
+ default=DEFAULT_TILT_OPTIMISTIC): cv.boolean,
+ vol.Optional(CONF_TILT_STATUS_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
+ vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema), validate_options)
@@ -199,10 +190,10 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def _setup_from_config(self, config):
self._config = config
- self._optimistic = (config.get(CONF_OPTIMISTIC) or
+ self._optimistic = (config[CONF_OPTIMISTIC] or
(config.get(CONF_STATE_TOPIC) is None and
config.get(CONF_GET_POSITION_TOPIC) is None))
- self._tilt_optimistic = config.get(CONF_TILT_STATE_OPTIMISTIC)
+ self._tilt_optimistic = config[CONF_TILT_STATE_OPTIMISTIC]
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@@ -219,8 +210,8 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def tilt_updated(msg):
"""Handle tilt updates."""
if (msg.payload.isnumeric() and
- (self._config.get(CONF_TILT_MIN) <= int(msg.payload) <=
- self._config.get(CONF_TILT_MAX))):
+ (self._config[CONF_TILT_MIN] <= int(msg.payload) <=
+ self._config[CONF_TILT_MAX])):
level = self.find_percentage_in_range(float(msg.payload))
self._tilt_value = level
@@ -234,9 +225,9 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
payload = template.async_render_with_possible_json_value(
payload)
- if payload == self._config.get(CONF_STATE_OPEN):
+ if payload == self._config[CONF_STATE_OPEN]:
self._state = False
- elif payload == self._config.get(CONF_STATE_CLOSED):
+ elif payload == self._config[CONF_STATE_CLOSED]:
self._state = True
else:
_LOGGER.warning("Payload is not True or False: %s", payload)
@@ -267,12 +258,12 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics['get_position_topic'] = {
'topic': self._config.get(CONF_GET_POSITION_TOPIC),
'msg_callback': position_message_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
elif self._config.get(CONF_STATE_TOPIC):
topics['state_topic'] = {
'topic': self._config.get(CONF_STATE_TOPIC),
'msg_callback': state_message_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
else:
# Force into optimistic mode.
self._optimistic = True
@@ -285,7 +276,7 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics['tilt_status_topic'] = {
'topic': self._config.get(CONF_TILT_STATUS_TOPIC),
'msg_callback': tilt_updated,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
@@ -311,7 +302,7 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the cover."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def is_closed(self):
@@ -358,14 +349,14 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_OPEN), self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_PAYLOAD_OPEN], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = False
if self._config.get(CONF_GET_POSITION_TOPIC):
self._position = self.find_percentage_in_range(
- self._config.get(CONF_POSITION_OPEN), COVER_PAYLOAD)
+ self._config[CONF_POSITION_OPEN], COVER_PAYLOAD)
self.async_write_ha_state()
async def async_close_cover(self, **kwargs):
@@ -375,14 +366,14 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_CLOSE), self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_PAYLOAD_CLOSE], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = True
if self._config.get(CONF_GET_POSITION_TOPIC):
self._position = self.find_percentage_in_range(
- self._config.get(CONF_POSITION_CLOSED), COVER_PAYLOAD)
+ self._config[CONF_POSITION_CLOSED], COVER_PAYLOAD)
self.async_write_ha_state()
async def async_stop_cover(self, **kwargs):
@@ -392,29 +383,29 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_STOP), self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_PAYLOAD_STOP], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
async def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open."""
mqtt.async_publish(self.hass,
self._config.get(CONF_TILT_COMMAND_TOPIC),
- self._config.get(CONF_TILT_OPEN_POSITION),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_TILT_OPEN_POSITION],
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._tilt_optimistic:
- self._tilt_value = self._config.get(CONF_TILT_OPEN_POSITION)
+ self._tilt_value = self._config[CONF_TILT_OPEN_POSITION]
self.async_write_ha_state()
async def async_close_cover_tilt(self, **kwargs):
"""Tilt the cover closed."""
mqtt.async_publish(self.hass,
self._config.get(CONF_TILT_COMMAND_TOPIC),
- self._config.get(CONF_TILT_CLOSED_POSITION),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_TILT_CLOSED_POSITION],
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._tilt_optimistic:
- self._tilt_value = self._config.get(CONF_TILT_CLOSED_POSITION)
+ self._tilt_value = self._config[CONF_TILT_CLOSED_POSITION]
self.async_write_ha_state()
async def async_set_cover_tilt_position(self, **kwargs):
@@ -430,8 +421,8 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(self.hass,
self._config.get(CONF_TILT_COMMAND_TOPIC),
level,
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
@@ -446,19 +437,19 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
- elif (self._config.get(CONF_POSITION_OPEN) != 100 and
- self._config.get(CONF_POSITION_CLOSED) != 0):
+ elif (self._config[CONF_POSITION_OPEN] != 100 and
+ self._config[CONF_POSITION_CLOSED] != 0):
position = self.find_in_range_from_percent(
position, COVER_PAYLOAD)
mqtt.async_publish(self.hass,
self._config.get(CONF_SET_POSITION_TOPIC),
position,
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic:
self._state = percentage_position == \
- self._config.get(CONF_POSITION_CLOSED)
+ self._config[CONF_POSITION_CLOSED]
self._position = percentage_position
self.async_write_ha_state()
@@ -466,11 +457,11 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""Find the 0-100% value within the specified range."""
# the range of motion as defined by the min max values
if range_type == COVER_PAYLOAD:
- max_range = self._config.get(CONF_POSITION_OPEN)
- min_range = self._config.get(CONF_POSITION_CLOSED)
+ max_range = self._config[CONF_POSITION_OPEN]
+ min_range = self._config[CONF_POSITION_CLOSED]
else:
- max_range = self._config.get(CONF_TILT_MAX)
- min_range = self._config.get(CONF_TILT_MIN)
+ max_range = self._config[CONF_TILT_MAX]
+ min_range = self._config[CONF_TILT_MIN]
current_range = max_range - min_range
# offset to be zero based
offset_position = position - min_range
@@ -482,7 +473,7 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
position_percentage = min(max(position_percentage, min_percent),
max_percent)
if range_type == TILT_PAYLOAD and \
- self._config.get(CONF_TILT_INVERT_STATE):
+ self._config[CONF_TILT_INVERT_STATE]:
return 100 - position_percentage
return position_percentage
@@ -496,18 +487,18 @@ class MqttCover(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
returning the offset
"""
if range_type == COVER_PAYLOAD:
- max_range = self._config.get(CONF_POSITION_OPEN)
- min_range = self._config.get(CONF_POSITION_CLOSED)
+ max_range = self._config[CONF_POSITION_OPEN]
+ min_range = self._config[CONF_POSITION_CLOSED]
else:
- max_range = self._config.get(CONF_TILT_MAX)
- min_range = self._config.get(CONF_TILT_MIN)
+ max_range = self._config[CONF_TILT_MAX]
+ min_range = self._config[CONF_TILT_MIN]
offset = min_range
current_range = max_range - min_range
position = round(current_range * (percentage / 100.0))
position += offset
if range_type == TILT_PAYLOAD and \
- self._config.get(CONF_TILT_INVERT_STATE):
+ self._config[CONF_TILT_INVERT_STATE]:
position = max_range - position + offset
return position
diff --git a/homeassistant/components/mqtt/device_tracker.py b/homeassistant/components/mqtt/device_tracker.py
index 0f22ed150ca..25528471d64 100644
--- a/homeassistant/components/mqtt/device_tracker.py
+++ b/homeassistant/components/mqtt/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for tracking MQTT enabled devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.mqtt/
-"""
+"""Support for tracking MQTT enabled devices."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from . import CONF_QOS
-DEPENDENCIES = ['mqtt']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py
index cb87a208b4f..4c1427d7e15 100644
--- a/homeassistant/components/mqtt/discovery.py
+++ b/homeassistant/components/mqtt/discovery.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT discovery.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/mqtt/#discovery
-"""
+"""Support for MQTT discovery."""
import asyncio
import json
import logging
@@ -15,7 +10,7 @@ from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import HomeAssistantType
-from . import ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC
+from .const import ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC
_LOGGER = logging.getLogger(__name__)
@@ -24,21 +19,30 @@ TOPIC_MATCHER = re.compile(
r'(?:(?P[a-zA-Z0-9_-]+)/)?(?P[a-zA-Z0-9_-]+)/config')
SUPPORTED_COMPONENTS = [
- 'binary_sensor', 'camera', 'cover', 'fan',
- 'light', 'sensor', 'switch', 'lock', 'climate',
- 'alarm_control_panel', 'vacuum']
-
-CONFIG_ENTRY_COMPONENTS = [
+ 'alarm_control_panel',
'binary_sensor',
'camera',
+ 'climate',
'cover',
+ 'fan',
'light',
'lock',
'sensor',
'switch',
- 'climate',
+ 'vacuum',
+]
+
+CONFIG_ENTRY_COMPONENTS = [
'alarm_control_panel',
+ 'binary_sensor',
+ 'camera',
+ 'climate',
+ 'cover',
'fan',
+ 'light',
+ 'lock',
+ 'sensor',
+ 'switch',
'vacuum',
]
@@ -49,6 +53,14 @@ DEPRECATED_PLATFORM_TO_SCHEMA = {
}
}
+# These components require state_topic to be set.
+# If not specified, infer state_topic from discovery topic.
+IMPLICIT_STATE_TOPIC_COMPONENTS = [
+ 'alarm_control_panel',
+ 'binary_sensor',
+ 'sensor',
+]
+
ALREADY_DISCOVERED = 'mqtt_discovered_components'
DATA_CONFIG_ENTRY_LOCK = 'mqtt_config_entry_lock'
@@ -263,10 +275,16 @@ async def async_start(hass: HomeAssistantType, discovery_topic, hass_config,
platform, schema)
payload[CONF_PLATFORM] = 'mqtt'
- if CONF_STATE_TOPIC not in payload:
+ if (CONF_STATE_TOPIC not in payload and
+ component in IMPLICIT_STATE_TOPIC_COMPONENTS):
+ # state_topic not specified, infer from discovery topic
payload[CONF_STATE_TOPIC] = '{}/{}/{}{}/state'.format(
discovery_topic, component,
'%s/' % node_id if node_id else '', object_id)
+ _LOGGER.warning('implicit %s is deprecated, add "%s":"%s" to '
+ '%s discovery message',
+ CONF_STATE_TOPIC, CONF_STATE_TOPIC,
+ payload[CONF_STATE_TOPIC], topic)
payload[ATTR_DISCOVERY_HASH] = discovery_hash
diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py
index b8bff6088d8..99aa68d1975 100644
--- a/homeassistant/components/mqtt/fan.py
+++ b/homeassistant/components/mqtt/fan.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT fans.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/fan.mqtt/
-"""
+"""Support for MQTT fans."""
import logging
import voluptuous as vol
@@ -28,8 +23,6 @@ from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
CONF_STATE_VALUE_TEMPLATE = 'state_value_template'
CONF_SPEED_STATE_TOPIC = 'speed_state_topic'
CONF_SPEED_COMMAND_TOPIC = 'speed_command_topic'
@@ -55,29 +48,29 @@ OSCILLATE_OFF_PAYLOAD = 'oscillate_off'
OSCILLATION = 'oscillation'
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
- vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template,
- vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template,
- vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
- vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
- vol.Optional(CONF_PAYLOAD_OSCILLATION_ON,
- default=DEFAULT_PAYLOAD_ON): cv.string,
- vol.Optional(CONF_PAYLOAD_OSCILLATION_OFF,
- default=DEFAULT_PAYLOAD_OFF): cv.string,
+ vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string,
vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string,
vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string,
- vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string,
+ vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
+ vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
+ vol.Optional(CONF_PAYLOAD_OSCILLATION_OFF,
+ default=DEFAULT_PAYLOAD_OFF): cv.string,
+ vol.Optional(CONF_PAYLOAD_OSCILLATION_ON,
+ default=DEFAULT_PAYLOAD_ON): cv.string,
+ vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SPEED_LIST,
default=[SPEED_OFF, SPEED_LOW,
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
- vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
+ vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template,
+ vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -179,15 +172,15 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE)
}
self._payload = {
- STATE_ON: config.get(CONF_PAYLOAD_ON),
- STATE_OFF: config.get(CONF_PAYLOAD_OFF),
- OSCILLATE_ON_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_ON),
- OSCILLATE_OFF_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_OFF),
- SPEED_LOW: config.get(CONF_PAYLOAD_LOW_SPEED),
- SPEED_MEDIUM: config.get(CONF_PAYLOAD_MEDIUM_SPEED),
- SPEED_HIGH: config.get(CONF_PAYLOAD_HIGH_SPEED),
+ STATE_ON: config[CONF_PAYLOAD_ON],
+ STATE_OFF: config[CONF_PAYLOAD_OFF],
+ OSCILLATE_ON_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_ON],
+ OSCILLATE_OFF_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_OFF],
+ SPEED_LOW: config[CONF_PAYLOAD_LOW_SPEED],
+ SPEED_MEDIUM: config[CONF_PAYLOAD_MEDIUM_SPEED],
+ SPEED_HIGH: config[CONF_PAYLOAD_HIGH_SPEED],
}
- optimistic = config.get(CONF_OPTIMISTIC)
+ optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
self._optimistic_oscillation = (
optimistic or self._topic[CONF_OSCILLATION_STATE_TOPIC] is None)
@@ -225,7 +218,7 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_STATE_TOPIC] = {
'topic': self._topic[CONF_STATE_TOPIC],
'msg_callback': state_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
@callback
def speed_received(msg):
@@ -243,7 +236,7 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_SPEED_STATE_TOPIC] = {
'topic': self._topic[CONF_SPEED_STATE_TOPIC],
'msg_callback': speed_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._speed = SPEED_OFF
@callback
@@ -260,7 +253,7 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_OSCILLATION_STATE_TOPIC] = {
'topic': self._topic[CONF_OSCILLATION_STATE_TOPIC],
'msg_callback': oscillation_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._oscillation = False
self._sub_state = await subscription.async_subscribe_topics(
@@ -292,12 +285,12 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self) -> str:
"""Get entity name."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
- return self._config.get(CONF_SPEED_LIST)
+ return self._config[CONF_SPEED_LIST]
@property
def supported_features(self) -> int:
@@ -321,8 +314,8 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
- self._payload[STATE_ON], self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._payload[STATE_ON], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if speed:
await self.async_set_speed(speed)
@@ -333,8 +326,8 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
- self._payload[STATE_OFF], self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._payload[STATE_OFF], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
async def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan.
@@ -355,8 +348,8 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_SPEED_COMMAND_TOPIC],
- mqtt_payload, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ mqtt_payload, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_speed:
self._speed = speed
@@ -377,7 +370,7 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_OSCILLATION_COMMAND_TOPIC],
- payload, self._config.get(CONF_QOS), self._config.get(CONF_RETAIN))
+ payload, self._config[CONF_QOS], self._config[CONF_RETAIN])
if self._optimistic_oscillation:
self._oscillation = oscillating
diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py
index 4ff6efb8643..d115f07ce7e 100644
--- a/homeassistant/components/mqtt/light/__init__.py
+++ b/homeassistant/components/mqtt/light/__init__.py
@@ -17,8 +17,6 @@ from homeassistant.helpers.typing import HomeAssistantType, ConfigType
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
CONF_SCHEMA = 'schema'
diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py
index a985a707485..382effe837b 100644
--- a/homeassistant/components/mqtt/light/schema_basic.py
+++ b/homeassistant/components/mqtt/light/schema_basic.py
@@ -30,8 +30,6 @@ from . import MQTT_LIGHT_SCHEMA_SCHEMA
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
CONF_BRIGHTNESS_COMMAND_TOPIC = 'brightness_command_topic'
CONF_BRIGHTNESS_SCALE = 'brightness_scale'
CONF_BRIGHTNESS_STATE_TOPIC = 'brightness_state_topic'
@@ -81,6 +79,7 @@ PLATFORM_SCHEMA_BASIC = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_EFFECT_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
@@ -89,7 +88,8 @@ PLATFORM_SCHEMA_BASIC = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_UNIQUE_ID): cv.string,
+ vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE):
+ vol.In(VALUES_ON_COMMAND_TYPE),
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
@@ -98,6 +98,7 @@ PLATFORM_SCHEMA_BASIC = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_WHITE_VALUE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_WHITE_VALUE_SCALE, default=DEFAULT_WHITE_VALUE_SCALE):
vol.All(vol.Coerce(int), vol.Range(min=1)),
@@ -106,9 +107,6 @@ PLATFORM_SCHEMA_BASIC = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_XY_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_XY_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template,
- vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE):
- vol.In(VALUES_ON_COMMAND_TYPE),
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema).extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
@@ -202,8 +200,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
}
self._topic = topic
self._payload = {
- 'on': config.get(CONF_PAYLOAD_ON),
- 'off': config.get(CONF_PAYLOAD_OFF),
+ 'on': config[CONF_PAYLOAD_ON],
+ 'off': config[CONF_PAYLOAD_OFF],
}
self._templates = {
CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
@@ -219,7 +217,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
CONF_XY: config.get(CONF_XY_VALUE_TEMPLATE),
}
- optimistic = config.get(CONF_OPTIMISTIC)
+ optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None
self._optimistic_rgb = \
optimistic or topic[CONF_RGB_STATE_TOPIC] is None
@@ -272,7 +270,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_STATE_TOPIC] = {
'topic': self._topic[CONF_STATE_TOPIC],
'msg_callback': state_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
elif self._optimistic and last_state:
self._state = last_state.state == STATE_ON
@@ -287,7 +285,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
device_value = float(payload)
percent_bright = \
- device_value / self._config.get(CONF_BRIGHTNESS_SCALE)
+ device_value / self._config[CONF_BRIGHTNESS_SCALE]
self._brightness = percent_bright * 255
self.async_write_ha_state()
@@ -295,7 +293,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_BRIGHTNESS_STATE_TOPIC] = {
'topic': self._topic[CONF_BRIGHTNESS_STATE_TOPIC],
'msg_callback': brightness_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._brightness = 255
elif self._optimistic_brightness and last_state\
and last_state.attributes.get(ATTR_BRIGHTNESS):
@@ -326,7 +324,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_RGB_STATE_TOPIC] = {
'topic': self._topic[CONF_RGB_STATE_TOPIC],
'msg_callback': rgb_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._hs = (0, 0)
if self._optimistic_rgb and last_state\
and last_state.attributes.get(ATTR_HS_COLOR):
@@ -350,7 +348,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_COLOR_TEMP_STATE_TOPIC] = {
'topic': self._topic[CONF_COLOR_TEMP_STATE_TOPIC],
'msg_callback': color_temp_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._color_temp = 150
if self._optimistic_color_temp and last_state\
and last_state.attributes.get(ATTR_COLOR_TEMP):
@@ -376,7 +374,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_EFFECT_STATE_TOPIC] = {
'topic': self._topic[CONF_EFFECT_STATE_TOPIC],
'msg_callback': effect_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._effect = 'none'
if self._optimistic_effect and last_state\
and last_state.attributes.get(ATTR_EFFECT):
@@ -406,7 +404,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_HS_STATE_TOPIC] = {
'topic': self._topic[CONF_HS_STATE_TOPIC],
'msg_callback': hs_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._hs = (0, 0)
if self._optimistic_hs and last_state\
and last_state.attributes.get(ATTR_HS_COLOR):
@@ -425,7 +423,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
device_value = float(payload)
percent_white = \
- device_value / self._config.get(CONF_WHITE_VALUE_SCALE)
+ device_value / self._config[CONF_WHITE_VALUE_SCALE]
self._white_value = percent_white * 255
self.async_write_ha_state()
@@ -433,7 +431,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_WHITE_VALUE_STATE_TOPIC] = {
'topic': self._topic[CONF_WHITE_VALUE_STATE_TOPIC],
'msg_callback': white_value_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._white_value = 255
elif self._optimistic_white_value and last_state\
and last_state.attributes.get(ATTR_WHITE_VALUE):
@@ -460,7 +458,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
topics[CONF_XY_STATE_TOPIC] = {
'topic': self._topic[CONF_XY_STATE_TOPIC],
'msg_callback': xy_received,
- 'qos': self._config.get(CONF_QOS)}
+ 'qos': self._config[CONF_QOS]}
self._hs = (0, 0)
if self._optimistic_xy and last_state\
and last_state.attributes.get(ATTR_HS_COLOR):
@@ -513,7 +511,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the device if any."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unique_id(self):
@@ -572,13 +570,13 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
This method is a coroutine.
"""
should_update = False
- on_command_type = self._config.get(CONF_ON_COMMAND_TYPE)
+ on_command_type = self._config[CONF_ON_COMMAND_TYPE]
if on_command_type == 'first':
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
- self._payload['on'], self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._payload['on'], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
should_update = True
# If brightness is being used instead of an on command, make sure
@@ -616,8 +614,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_RGB_COMMAND_TOPIC],
- rgb_color_str, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ rgb_color_str, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_rgb:
self._hs = kwargs[ATTR_HS_COLOR]
@@ -629,8 +627,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
hs_color = kwargs[ATTR_HS_COLOR]
mqtt.async_publish(
self.hass, self._topic[CONF_HS_COMMAND_TOPIC],
- '{},{}'.format(*hs_color), self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ '{},{}'.format(*hs_color), self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_hs:
self._hs = kwargs[ATTR_HS_COLOR]
@@ -642,8 +640,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
xy_color = color_util.color_hs_to_xy(*kwargs[ATTR_HS_COLOR])
mqtt.async_publish(
self.hass, self._topic[CONF_XY_COMMAND_TOPIC],
- '{},{}'.format(*xy_color), self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ '{},{}'.format(*xy_color), self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_xy:
self._hs = kwargs[ATTR_HS_COLOR]
@@ -652,13 +650,13 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if ATTR_BRIGHTNESS in kwargs and \
self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None:
percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255
- brightness_scale = self._config.get(CONF_BRIGHTNESS_SCALE)
+ brightness_scale = self._config[CONF_BRIGHTNESS_SCALE]
device_brightness = \
min(round(percent_bright * brightness_scale), brightness_scale)
mqtt.async_publish(
self.hass, self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC],
- device_brightness, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ device_brightness, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
@@ -679,8 +677,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_RGB_COMMAND_TOPIC],
- rgb_color_str, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ rgb_color_str, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
@@ -698,8 +696,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC],
- color_temp, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ color_temp, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_color_temp:
self._color_temp = kwargs[ATTR_COLOR_TEMP]
@@ -711,8 +709,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if effect in self._config.get(CONF_EFFECT_LIST):
mqtt.async_publish(
self.hass, self._topic[CONF_EFFECT_COMMAND_TOPIC],
- effect, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ effect, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_effect:
self._effect = kwargs[ATTR_EFFECT]
@@ -721,13 +719,13 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if ATTR_WHITE_VALUE in kwargs and \
self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None:
percent_white = float(kwargs[ATTR_WHITE_VALUE]) / 255
- white_scale = self._config.get(CONF_WHITE_VALUE_SCALE)
+ white_scale = self._config[CONF_WHITE_VALUE_SCALE]
device_white_value = \
min(round(percent_white * white_scale), white_scale)
mqtt.async_publish(
self.hass, self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC],
- device_white_value, self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ device_white_value, self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic_white_value:
self._white_value = kwargs[ATTR_WHITE_VALUE]
@@ -735,8 +733,8 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if on_command_type == 'last':
mqtt.async_publish(self.hass, self._topic[CONF_COMMAND_TOPIC],
- self._payload['on'], self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._payload['on'], self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
should_update = True
if self._optimistic:
@@ -754,7 +752,7 @@ class MqttLight(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC], self._payload['off'],
- self._config.get(CONF_QOS), self._config.get(CONF_RETAIN))
+ self._config[CONF_QOS], self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that the light has changed state.
diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py
index 12f688afbf7..27c88edb15f 100644
--- a/homeassistant/components/mqtt/light/schema_json.py
+++ b/homeassistant/components/mqtt/light/schema_json.py
@@ -35,8 +35,6 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'mqtt_json'
-DEPENDENCIES = ['mqtt']
-
DEFAULT_BRIGHTNESS = False
DEFAULT_COLOR_TEMP = False
DEFAULT_EFFECT = False
@@ -62,25 +60,24 @@ PLATFORM_SCHEMA_JSON = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
- vol.Optional(CONF_FLASH_TIME_SHORT, default=DEFAULT_FLASH_TIME_SHORT):
- cv.positive_int,
vol.Optional(CONF_FLASH_TIME_LONG, default=DEFAULT_FLASH_TIME_LONG):
cv.positive_int,
+ vol.Optional(CONF_FLASH_TIME_SHORT, default=DEFAULT_FLASH_TIME_SHORT):
+ cv.positive_int,
+ vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS):
vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean,
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
- vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean,
- vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema).extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
@@ -148,34 +145,34 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
CONF_COMMAND_TOPIC
)
}
- optimistic = config.get(CONF_OPTIMISTIC)
+ optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
- brightness = config.get(CONF_BRIGHTNESS)
+ brightness = config[CONF_BRIGHTNESS]
if brightness:
self._brightness = 255
else:
self._brightness = None
- color_temp = config.get(CONF_COLOR_TEMP)
+ color_temp = config[CONF_COLOR_TEMP]
if color_temp:
self._color_temp = 150
else:
self._color_temp = None
- effect = config.get(CONF_EFFECT)
+ effect = config[CONF_EFFECT]
if effect:
self._effect = 'none'
else:
self._effect = None
- white_value = config.get(CONF_WHITE_VALUE)
+ white_value = config[CONF_WHITE_VALUE]
if white_value:
self._white_value = 255
else:
self._white_value = None
- if config.get(CONF_HS) or config.get(CONF_RGB) or config.get(CONF_XY):
+ if config[CONF_HS] or config[CONF_RGB] or config[CONF_XY]:
self._hs = [0, 0]
else:
self._hs = None
@@ -188,13 +185,13 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
}
self._supported_features = (SUPPORT_TRANSITION | SUPPORT_FLASH)
- self._supported_features |= (config.get(CONF_RGB) and SUPPORT_COLOR)
+ self._supported_features |= (config[CONF_RGB] and SUPPORT_COLOR)
self._supported_features |= (brightness and SUPPORT_BRIGHTNESS)
self._supported_features |= (color_temp and SUPPORT_COLOR_TEMP)
self._supported_features |= (effect and SUPPORT_EFFECT)
self._supported_features |= (white_value and SUPPORT_WHITE_VALUE)
- self._supported_features |= (config.get(CONF_XY) and SUPPORT_COLOR)
- self._supported_features |= (config.get(CONF_HS) and SUPPORT_COLOR)
+ self._supported_features |= (config[CONF_XY] and SUPPORT_COLOR)
+ self._supported_features |= (config[CONF_HS] and SUPPORT_COLOR)
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@@ -246,7 +243,7 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
try:
self._brightness = int(
values['brightness'] /
- float(self._config.get(CONF_BRIGHTNESS_SCALE)) * 255)
+ float(self._config[CONF_BRIGHTNESS_SCALE]) * 255)
except KeyError:
pass
except ValueError:
@@ -283,7 +280,7 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self.hass, self._sub_state,
{'state_topic': {'topic': self._topic[CONF_STATE_TOPIC],
'msg_callback': state_received,
- 'qos': self._config.get(CONF_QOS)}})
+ 'qos': self._config[CONF_QOS]}})
if self._optimistic and last_state:
self._state = last_state.state == STATE_ON
@@ -343,7 +340,7 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the device if any."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unique_id(self):
@@ -375,11 +372,11 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
message = {'state': 'ON'}
if ATTR_HS_COLOR in kwargs and (
- self._config.get(CONF_HS) or self._config.get(CONF_RGB)
- or self._config.get(CONF_XY)):
+ self._config[CONF_HS] or self._config[CONF_RGB]
+ or self._config[CONF_XY]):
hs_color = kwargs[ATTR_HS_COLOR]
message['color'] = {}
- if self._config.get(CONF_RGB):
+ if self._config[CONF_RGB]:
# If there's a brightness topic set, we don't want to scale the
# RGB values given using the brightness.
if self._brightness is not None:
@@ -393,11 +390,11 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
message['color']['r'] = rgb[0]
message['color']['g'] = rgb[1]
message['color']['b'] = rgb[2]
- if self._config.get(CONF_XY):
+ if self._config[CONF_XY]:
xy_color = color_util.color_hs_to_xy(*kwargs[ATTR_HS_COLOR])
message['color']['x'] = xy_color[0]
message['color']['y'] = xy_color[1]
- if self._config.get(CONF_HS):
+ if self._config[CONF_HS]:
message['color']['h'] = hs_color[0]
message['color']['s'] = hs_color[1]
@@ -416,10 +413,10 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
if ATTR_TRANSITION in kwargs:
message['transition'] = int(kwargs[ATTR_TRANSITION])
- if ATTR_BRIGHTNESS in kwargs:
+ if ATTR_BRIGHTNESS in kwargs and self._brightness is not None:
message['brightness'] = int(
kwargs[ATTR_BRIGHTNESS] / float(DEFAULT_BRIGHTNESS_SCALE) *
- self._config.get(CONF_BRIGHTNESS_SCALE))
+ self._config[CONF_BRIGHTNESS_SCALE])
if self._optimistic:
self._brightness = kwargs[ATTR_BRIGHTNESS]
@@ -448,7 +445,7 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC], json.dumps(message),
- self._config.get(CONF_QOS), self._config.get(CONF_RETAIN))
+ self._config[CONF_QOS], self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that the light has changed state.
@@ -470,7 +467,7 @@ class MqttLightJson(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC], json.dumps(message),
- self._config.get(CONF_QOS), self._config.get(CONF_RETAIN))
+ self._config[CONF_QOS], self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that the light has changed state.
diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py
index 27c1fb00441..ab9fb0e4454 100644
--- a/homeassistant/components/mqtt/light/schema_template.py
+++ b/homeassistant/components/mqtt/light/schema_template.py
@@ -30,8 +30,6 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'mqtt_template'
-DEPENDENCIES = ['mqtt']
-
DEFAULT_NAME = 'MQTT Template Light'
DEFAULT_OPTIMISTIC = False
@@ -51,23 +49,18 @@ PLATFORM_SCHEMA_TEMPLATE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BLUE_TEMPLATE): cv.template,
vol.Optional(CONF_BRIGHTNESS_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template,
+ vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template,
+ vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_TEMPLATE): cv.template,
vol.Optional(CONF_GREEN_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RED_TEMPLATE): cv.template,
- vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
- vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template,
- vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template,
- vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template,
- vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS):
- vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema).extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
@@ -150,7 +143,7 @@ class MqttTemplate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
CONF_WHITE_VALUE_TEMPLATE,
)
}
- optimistic = config.get(CONF_OPTIMISTIC)
+ optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic \
or self._topics[CONF_STATE_TOPIC] is None \
or self._templates[CONF_STATE_TEMPLATE] is None
@@ -257,7 +250,7 @@ class MqttTemplate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self.hass, self._sub_state,
{'state_topic': {'topic': self._topics[CONF_STATE_TOPIC],
'msg_callback': state_received,
- 'qos': self._config.get(CONF_QOS)}})
+ 'qos': self._config[CONF_QOS]}})
if self._optimistic and last_state:
self._state = last_state.state == STATE_ON
@@ -310,7 +303,7 @@ class MqttTemplate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the entity."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unique_id(self):
@@ -396,7 +389,7 @@ class MqttTemplate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topics[CONF_COMMAND_TOPIC],
self._templates[CONF_COMMAND_ON_TEMPLATE].async_render(**values),
- self._config.get(CONF_QOS), self._config.get(CONF_RETAIN)
+ self._config[CONF_QOS], self._config[CONF_RETAIN]
)
if self._optimistic:
@@ -417,7 +410,7 @@ class MqttTemplate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
mqtt.async_publish(
self.hass, self._topics[CONF_COMMAND_TOPIC],
self._templates[CONF_COMMAND_OFF_TEMPLATE].async_render(**values),
- self._config.get(CONF_QOS), self._config.get(CONF_RETAIN)
+ self._config[CONF_QOS], self._config[CONF_RETAIN]
)
if self._optimistic:
diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py
index ee459d2174f..75db4c3742d 100644
--- a/homeassistant/components/mqtt/lock.py
+++ b/homeassistant/components/mqtt/lock.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT locks.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/lock.mqtt/
-"""
+"""Support for MQTT locks."""
import logging
import voluptuous as vol
@@ -32,17 +27,15 @@ DEFAULT_NAME = 'MQTT Lock'
DEFAULT_OPTIMISTIC = False
DEFAULT_PAYLOAD_LOCK = 'LOCK'
DEFAULT_PAYLOAD_UNLOCK = 'UNLOCK'
-DEPENDENCIES = ['mqtt']
-
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK):
cv.string,
vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK):
cv.string,
- vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -84,12 +77,14 @@ class MqttLock(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def __init__(self, config, config_entry, discovery_hash):
"""Initialize the lock."""
- self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._state = False
self._sub_state = None
self._optimistic = False
+ # Load config
+ self._setup_from_config(config)
+
device_config = config.get(CONF_DEVICE)
MqttAttributes.__init__(self, config)
@@ -106,13 +101,19 @@ class MqttLock(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
config = PLATFORM_SCHEMA(discovery_payload)
- self._config = config
+ self._setup_from_config(config)
await self.attributes_discovery_update(config)
await self.availability_discovery_update(config)
await self.device_info_discovery_update(config)
await self._subscribe_topics()
self.async_write_ha_state()
+ def _setup_from_config(self, config):
+ """(Re)Setup the entity."""
+ self._config = config
+
+ self._optimistic = config[CONF_OPTIMISTIC]
+
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
value_template = self._config.get(CONF_VALUE_TEMPLATE)
diff --git a/homeassistant/components/mqtt/manifest.json b/homeassistant/components/mqtt/manifest.json
new file mode 100644
index 00000000000..dd4d0323a51
--- /dev/null
+++ b/homeassistant/components/mqtt/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "mqtt",
+ "name": "MQTT",
+ "documentation": "https://www.home-assistant.io/components/mqtt",
+ "requirements": [
+ "hbmqtt==0.9.4",
+ "paho-mqtt==1.4.0"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py
index aa8d5e2a31e..02dafdb57c1 100644
--- a/homeassistant/components/mqtt/sensor.py
+++ b/homeassistant/components/mqtt/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mqtt/
-"""
+"""Support for MQTT sensors."""
from datetime import timedelta
import json
import logging
@@ -37,20 +32,16 @@ CONF_JSON_ATTRS = 'json_attributes'
DEFAULT_NAME = 'MQTT Sensor'
DEFAULT_FORCE_UPDATE = False
-DEPENDENCIES = ['mqtt']
-
PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
- vol.Optional(CONF_ICON): cv.icon,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
- vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv,
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
- # Integrations should never expose unique_id through configuration.
- # This is an exception because MQTT is a message transport, not a protocol.
+ vol.Optional(CONF_ICON): cv.icon,
+ vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -151,7 +142,7 @@ class MqttSensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._expiration_trigger = async_track_point_in_utc_time(
self.hass, self.value_is_expired, expiration_at)
- json_attributes = set(self._config.get(CONF_JSON_ATTRS))
+ json_attributes = set(self._config[CONF_JSON_ATTRS])
if json_attributes:
self._attributes = {}
try:
@@ -174,9 +165,9 @@ class MqttSensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
- {'state_topic': {'topic': self._config.get(CONF_STATE_TOPIC),
+ {'state_topic': {'topic': self._config[CONF_STATE_TOPIC],
'msg_callback': message_received,
- 'qos': self._config.get(CONF_QOS)}})
+ 'qos': self._config[CONF_QOS]}})
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
@@ -200,7 +191,7 @@ class MqttSensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the sensor."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def unit_of_measurement(self):
@@ -210,7 +201,7 @@ class MqttSensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def force_update(self):
"""Force update."""
- return self._config.get(CONF_FORCE_UPDATE)
+ return self._config[CONF_FORCE_UPDATE]
@property
def state(self):
diff --git a/homeassistant/components/mqtt/server.py b/homeassistant/components/mqtt/server.py
index 3373149a013..8944aba2dae 100644
--- a/homeassistant/components/mqtt/server.py
+++ b/homeassistant/components/mqtt/server.py
@@ -1,9 +1,4 @@
-"""
-Support for a local MQTT broker.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/mqtt/#use-the-embedded-broker
-"""
+"""Support for a local MQTT broker."""
import asyncio
import logging
import tempfile
@@ -13,12 +8,8 @@ import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['hbmqtt==0.9.4']
-
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
-
# None allows custom config to be created through generate_config
HBMQTT_CONFIG_SCHEMA = vol.Any(None, vol.Schema({
vol.Optional('auth'): vol.Schema({
diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py
index e159132d560..368a12c1956 100644
--- a/homeassistant/components/mqtt/subscription.py
+++ b/homeassistant/components/mqtt/subscription.py
@@ -1,9 +1,4 @@
-"""
-Helper to handle a set of topics to subscribe to.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/mqtt/
-"""
+"""Helper to handle a set of topics to subscribe to."""
import logging
from typing import Any, Callable, Dict, Optional
diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py
index 4847afd80c9..a9e3875aaea 100644
--- a/homeassistant/components/mqtt/switch.py
+++ b/homeassistant/components/mqtt/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.mqtt/
-"""
+"""Support for MQTT switches."""
import logging
import voluptuous as vol
@@ -27,8 +22,6 @@ from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
DEFAULT_NAME = 'MQTT Switch'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
@@ -37,15 +30,15 @@ CONF_STATE_ON = "state_on"
CONF_STATE_OFF = "state_off"
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_ICON): cv.icon,
- vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
- vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
- vol.Optional(CONF_STATE_ON): cv.string,
- vol.Optional(CONF_STATE_OFF): cv.string,
- vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Optional(CONF_ICON): cv.icon,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
+ vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
+ vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
+ vol.Optional(CONF_STATE_OFF): cv.string,
+ vol.Optional(CONF_STATE_ON): cv.string,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -128,13 +121,13 @@ class MqttSwitch(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._config = config
state_on = config.get(CONF_STATE_ON)
- self._state_on = state_on if state_on else config.get(CONF_PAYLOAD_ON)
+ self._state_on = state_on if state_on else config[CONF_PAYLOAD_ON]
state_off = config.get(CONF_STATE_OFF)
self._state_off = state_off if state_off else \
- config.get(CONF_PAYLOAD_OFF)
+ config[CONF_PAYLOAD_OFF]
- self._optimistic = config.get(CONF_OPTIMISTIC)
+ self._optimistic = config[CONF_OPTIMISTIC]
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@@ -165,7 +158,7 @@ class MqttSwitch(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
{CONF_STATE_TOPIC:
{'topic': self._config.get(CONF_STATE_TOPIC),
'msg_callback': state_message_received,
- 'qos': self._config.get(CONF_QOS)}})
+ 'qos': self._config[CONF_QOS]}})
if self._optimistic:
last_state = await self.async_get_last_state()
@@ -187,7 +180,7 @@ class MqttSwitch(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the switch."""
- return self._config.get(CONF_NAME)
+ return self._config[CONF_NAME]
@property
def is_on(self):
@@ -216,10 +209,10 @@ class MqttSwitch(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass,
- self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_ON),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_COMMAND_TOPIC],
+ self._config[CONF_PAYLOAD_ON],
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that switch has changed state.
self._state = True
@@ -232,10 +225,10 @@ class MqttSwitch(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass,
- self._config.get(CONF_COMMAND_TOPIC),
- self._config.get(CONF_PAYLOAD_OFF),
- self._config.get(CONF_QOS),
- self._config.get(CONF_RETAIN))
+ self._config[CONF_COMMAND_TOPIC],
+ self._config[CONF_PAYLOAD_OFF],
+ self._config[CONF_QOS],
+ self._config[CONF_RETAIN])
if self._optimistic:
# Optimistically assume that switch has changed state.
self._state = False
diff --git a/homeassistant/components/mqtt/vacuum.py b/homeassistant/components/mqtt/vacuum.py
index efa00821c1b..5895d52e9dc 100644
--- a/homeassistant/components/mqtt/vacuum.py
+++ b/homeassistant/components/mqtt/vacuum.py
@@ -1,10 +1,6 @@
-"""
-Support for a generic MQTT vacuum.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/vacuum.mqtt/
-"""
+"""Support for a generic MQTT vacuum."""
import logging
+import json
import voluptuous as vol
@@ -27,8 +23,6 @@ from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
SERVICE_TO_STRING = {
SUPPORT_TURN_ON: 'turn_on',
SUPPORT_TURN_OFF: 'turn_off',
@@ -70,82 +64,78 @@ ALL_SERVICES = DEFAULT_SERVICES | SUPPORT_PAUSE | SUPPORT_LOCATE |\
SUPPORT_FAN_SPEED | SUPPORT_SEND_COMMAND
CONF_SUPPORTED_FEATURES = ATTR_SUPPORTED_FEATURES
-CONF_PAYLOAD_TURN_ON = 'payload_turn_on'
-CONF_PAYLOAD_TURN_OFF = 'payload_turn_off'
-CONF_PAYLOAD_RETURN_TO_BASE = 'payload_return_to_base'
-CONF_PAYLOAD_STOP = 'payload_stop'
+CONF_BATTERY_LEVEL_TEMPLATE = 'battery_level_template'
+CONF_BATTERY_LEVEL_TOPIC = 'battery_level_topic'
+CONF_CHARGING_TEMPLATE = 'charging_template'
+CONF_CHARGING_TOPIC = 'charging_topic'
+CONF_CLEANING_TEMPLATE = 'cleaning_template'
+CONF_CLEANING_TOPIC = 'cleaning_topic'
+CONF_DOCKED_TEMPLATE = 'docked_template'
+CONF_DOCKED_TOPIC = 'docked_topic'
+CONF_ERROR_TEMPLATE = 'error_template'
+CONF_ERROR_TOPIC = 'error_topic'
+CONF_FAN_SPEED_LIST = 'fan_speed_list'
+CONF_FAN_SPEED_TEMPLATE = 'fan_speed_template'
+CONF_FAN_SPEED_TOPIC = 'fan_speed_topic'
CONF_PAYLOAD_CLEAN_SPOT = 'payload_clean_spot'
CONF_PAYLOAD_LOCATE = 'payload_locate'
+CONF_PAYLOAD_RETURN_TO_BASE = 'payload_return_to_base'
CONF_PAYLOAD_START_PAUSE = 'payload_start_pause'
-CONF_BATTERY_LEVEL_TOPIC = 'battery_level_topic'
-CONF_BATTERY_LEVEL_TEMPLATE = 'battery_level_template'
-CONF_CHARGING_TOPIC = 'charging_topic'
-CONF_CHARGING_TEMPLATE = 'charging_template'
-CONF_CLEANING_TOPIC = 'cleaning_topic'
-CONF_CLEANING_TEMPLATE = 'cleaning_template'
-CONF_DOCKED_TOPIC = 'docked_topic'
-CONF_DOCKED_TEMPLATE = 'docked_template'
-CONF_ERROR_TOPIC = 'error_topic'
-CONF_ERROR_TEMPLATE = 'error_template'
-CONF_STATE_TOPIC = 'state_topic'
-CONF_STATE_TEMPLATE = 'state_template'
-CONF_FAN_SPEED_TOPIC = 'fan_speed_topic'
-CONF_FAN_SPEED_TEMPLATE = 'fan_speed_template'
-CONF_SET_FAN_SPEED_TOPIC = 'set_fan_speed_topic'
-CONF_FAN_SPEED_LIST = 'fan_speed_list'
+CONF_PAYLOAD_STOP = 'payload_stop'
+CONF_PAYLOAD_TURN_OFF = 'payload_turn_off'
+CONF_PAYLOAD_TURN_ON = 'payload_turn_on'
CONF_SEND_COMMAND_TOPIC = 'send_command_topic'
+CONF_SET_FAN_SPEED_TOPIC = 'set_fan_speed_topic'
DEFAULT_NAME = 'MQTT Vacuum'
-DEFAULT_RETAIN = False
-DEFAULT_SERVICE_STRINGS = services_to_strings(DEFAULT_SERVICES)
-DEFAULT_PAYLOAD_TURN_ON = 'turn_on'
-DEFAULT_PAYLOAD_TURN_OFF = 'turn_off'
-DEFAULT_PAYLOAD_RETURN_TO_BASE = 'return_to_base'
-DEFAULT_PAYLOAD_STOP = 'stop'
DEFAULT_PAYLOAD_CLEAN_SPOT = 'clean_spot'
DEFAULT_PAYLOAD_LOCATE = 'locate'
+DEFAULT_PAYLOAD_RETURN_TO_BASE = 'return_to_base'
DEFAULT_PAYLOAD_START_PAUSE = 'start_pause'
+DEFAULT_PAYLOAD_STOP = 'stop'
+DEFAULT_PAYLOAD_TURN_OFF = 'turn_off'
+DEFAULT_PAYLOAD_TURN_ON = 'turn_on'
+DEFAULT_RETAIN = False
+DEFAULT_SERVICE_STRINGS = services_to_strings(DEFAULT_SERVICES)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
+ vol.Inclusive(CONF_BATTERY_LEVEL_TEMPLATE, 'battery'): cv.template,
+ vol.Inclusive(CONF_BATTERY_LEVEL_TOPIC,
+ 'battery'): mqtt.valid_publish_topic,
+ vol.Inclusive(CONF_CHARGING_TEMPLATE, 'charging'): cv.template,
+ vol.Inclusive(CONF_CHARGING_TOPIC, 'charging'): mqtt.valid_publish_topic,
+ vol.Inclusive(CONF_CLEANING_TEMPLATE, 'cleaning'): cv.template,
+ vol.Inclusive(CONF_CLEANING_TOPIC, 'cleaning'): mqtt.valid_publish_topic,
+ vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Inclusive(CONF_DOCKED_TEMPLATE, 'docked'): cv.template,
+ vol.Inclusive(CONF_DOCKED_TOPIC, 'docked'): mqtt.valid_publish_topic,
+ vol.Inclusive(CONF_ERROR_TEMPLATE, 'error'): cv.template,
+ vol.Inclusive(CONF_ERROR_TOPIC, 'error'): mqtt.valid_publish_topic,
+ vol.Optional(CONF_FAN_SPEED_LIST, default=[]):
+ vol.All(cv.ensure_list, [cv.string]),
+ vol.Inclusive(CONF_FAN_SPEED_TEMPLATE, 'fan_speed'): cv.template,
+ vol.Inclusive(CONF_FAN_SPEED_TOPIC, 'fan_speed'): mqtt.valid_publish_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS):
- vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]),
- vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
- vol.Optional(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_PAYLOAD_TURN_ON,
- default=DEFAULT_PAYLOAD_TURN_ON): cv.string,
- vol.Optional(CONF_PAYLOAD_TURN_OFF,
- default=DEFAULT_PAYLOAD_TURN_OFF): cv.string,
- vol.Optional(CONF_PAYLOAD_RETURN_TO_BASE,
- default=DEFAULT_PAYLOAD_RETURN_TO_BASE): cv.string,
- vol.Optional(CONF_PAYLOAD_STOP,
- default=DEFAULT_PAYLOAD_STOP): cv.string,
vol.Optional(CONF_PAYLOAD_CLEAN_SPOT,
default=DEFAULT_PAYLOAD_CLEAN_SPOT): cv.string,
vol.Optional(CONF_PAYLOAD_LOCATE,
default=DEFAULT_PAYLOAD_LOCATE): cv.string,
+ vol.Optional(CONF_PAYLOAD_RETURN_TO_BASE,
+ default=DEFAULT_PAYLOAD_RETURN_TO_BASE): cv.string,
vol.Optional(CONF_PAYLOAD_START_PAUSE,
default=DEFAULT_PAYLOAD_START_PAUSE): cv.string,
- vol.Optional(CONF_BATTERY_LEVEL_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_BATTERY_LEVEL_TEMPLATE): cv.template,
- vol.Optional(CONF_CHARGING_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_CHARGING_TEMPLATE): cv.template,
- vol.Optional(CONF_CLEANING_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_CLEANING_TEMPLATE): cv.template,
- vol.Optional(CONF_DOCKED_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_DOCKED_TEMPLATE): cv.template,
- vol.Optional(CONF_ERROR_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_ERROR_TEMPLATE): cv.template,
- vol.Optional(CONF_STATE_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_STATE_TEMPLATE): cv.template,
- vol.Optional(CONF_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_FAN_SPEED_TEMPLATE): cv.template,
- vol.Optional(CONF_SET_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
- vol.Optional(CONF_FAN_SPEED_LIST, default=[]):
- vol.All(cv.ensure_list, [cv.string]),
+ vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
+ vol.Optional(CONF_PAYLOAD_TURN_OFF,
+ default=DEFAULT_PAYLOAD_TURN_OFF): cv.string,
+ vol.Optional(CONF_PAYLOAD_TURN_ON,
+ default=DEFAULT_PAYLOAD_TURN_ON): cv.string,
vol.Optional(CONF_SEND_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_SET_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS):
+ vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]),
vol.Optional(CONF_UNIQUE_ID): cv.string,
- vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
+ vol.Optional(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
+ vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
@@ -211,14 +201,14 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo.__init__(self, device_config, config_entry)
def _setup_from_config(self, config):
- self._name = config.get(CONF_NAME)
- supported_feature_strings = config.get(CONF_SUPPORTED_FEATURES)
+ self._name = config[CONF_NAME]
+ supported_feature_strings = config[CONF_SUPPORTED_FEATURES]
self._supported_features = strings_to_services(
supported_feature_strings
)
- self._fan_speed_list = config.get(CONF_FAN_SPEED_LIST)
- self._qos = config.get(mqtt.CONF_QOS)
- self._retain = config.get(mqtt.CONF_RETAIN)
+ self._fan_speed_list = config[CONF_FAN_SPEED_LIST]
+ self._qos = config[mqtt.CONF_QOS]
+ self._retain = config[mqtt.CONF_RETAIN]
self._command_topic = config.get(mqtt.CONF_COMMAND_TOPIC)
self._set_fan_speed_topic = config.get(CONF_SET_FAN_SPEED_TOPIC)
@@ -291,7 +281,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
battery_level = self._templates[CONF_BATTERY_LEVEL_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
- if battery_level is not None:
+ if battery_level:
self._battery_level = int(battery_level)
if msg.topic == self._state_topics[CONF_CHARGING_TOPIC] and \
@@ -299,7 +289,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
charging = self._templates[CONF_CHARGING_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
- if charging is not None:
+ if charging:
self._charging = cv.boolean(charging)
if msg.topic == self._state_topics[CONF_CLEANING_TOPIC] and \
@@ -307,7 +297,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
cleaning = self._templates[CONF_CLEANING_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
- if cleaning is not None:
+ if cleaning:
self._cleaning = cv.boolean(cleaning)
if msg.topic == self._state_topics[CONF_DOCKED_TOPIC] and \
@@ -315,7 +305,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
docked = self._templates[CONF_DOCKED_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
- if docked is not None:
+ if docked:
self._docked = cv.boolean(docked)
if msg.topic == self._state_topics[CONF_ERROR_TOPIC] and \
@@ -333,7 +323,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._status = "Docked"
elif self._cleaning:
self._status = "Cleaning"
- elif self._error is not None and not self._error:
+ elif self._error:
self._status = "Error: {}".format(self._error)
else:
self._status = "Stopped"
@@ -343,7 +333,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
fan_speed = self._templates[CONF_FAN_SPEED_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
- if fan_speed is not None:
+ if fan_speed:
self._fan_speed = fan_speed
self.async_write_ha_state()
@@ -518,8 +508,13 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""Send a command to a vacuum cleaner."""
if self.supported_features & SUPPORT_SEND_COMMAND == 0:
return
-
+ if params:
+ message = {"command": command}
+ message.update(params)
+ message = json.dumps(message)
+ else:
+ message = command
mqtt.async_publish(self.hass, self._send_command_topic,
- command, self._qos, self._retain)
- self._status = "Sending command {}...".format(command)
+ message, self._qos, self._retain)
+ self._status = "Sending command {}...".format(message)
self.async_write_ha_state()
diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py
index fb6a94f1870..0b54c8535a2 100644
--- a/homeassistant/components/mqtt_eventstream/__init__.py
+++ b/homeassistant/components/mqtt_eventstream/__init__.py
@@ -15,8 +15,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.json import JSONEncoder
DOMAIN = 'mqtt_eventstream'
-DEPENDENCIES = ['mqtt']
-
CONF_PUBLISH_TOPIC = 'publish_topic'
CONF_SUBSCRIBE_TOPIC = 'subscribe_topic'
CONF_PUBLISH_EVENTSTREAM_RECEIVED = 'publish_eventstream_received'
diff --git a/homeassistant/components/mqtt_eventstream/manifest.json b/homeassistant/components/mqtt_eventstream/manifest.json
new file mode 100644
index 00000000000..e795c8aaf18
--- /dev/null
+++ b/homeassistant/components/mqtt_eventstream/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mqtt_eventstream",
+ "name": "Mqtt eventstream",
+ "documentation": "https://www.home-assistant.io/components/mqtt_eventstream",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mqtt_json/device_tracker.py b/homeassistant/components/mqtt_json/device_tracker.py
index 0a1b327dca9..eed6f03615e 100644
--- a/homeassistant/components/mqtt_json/device_tracker.py
+++ b/homeassistant/components/mqtt_json/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for GPS tracking MQTT enabled devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.mqtt_json/
-"""
+"""Support for GPS tracking MQTT enabled devices."""
import json
import logging
@@ -18,8 +13,6 @@ from homeassistant.const import (
CONF_DEVICES, ATTR_GPS_ACCURACY, ATTR_LATITUDE,
ATTR_LONGITUDE, ATTR_BATTERY_LEVEL)
-DEPENDENCIES = ['mqtt']
-
_LOGGER = logging.getLogger(__name__)
GPS_JSON_PAYLOAD_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/mqtt_json/manifest.json b/homeassistant/components/mqtt_json/manifest.json
new file mode 100644
index 00000000000..a1986b2bf2e
--- /dev/null
+++ b/homeassistant/components/mqtt_json/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mqtt_json",
+ "name": "Mqtt json",
+ "documentation": "https://www.home-assistant.io/components/mqtt_json",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mqtt_room/manifest.json b/homeassistant/components/mqtt_room/manifest.json
new file mode 100644
index 00000000000..8fc90b0bcb1
--- /dev/null
+++ b/homeassistant/components/mqtt_room/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mqtt_room",
+ "name": "Mqtt room",
+ "documentation": "https://www.home-assistant.io/components/mqtt_room",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py
index 36f99719da4..37ea2697da1 100644
--- a/homeassistant/components/mqtt_room/sensor.py
+++ b/homeassistant/components/mqtt_room/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for MQTT room presence detection.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mqtt_room/
-"""
+"""Support for MQTT room presence detection."""
import logging
import json
from datetime import timedelta
@@ -22,8 +17,6 @@ from homeassistant.util import dt, slugify
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['mqtt']
-
ATTR_DEVICE_ID = 'device_id'
ATTR_DISTANCE = 'distance'
ATTR_ROOM = 'room'
diff --git a/homeassistant/components/mqtt_statestream/__init__.py b/homeassistant/components/mqtt_statestream/__init__.py
index 18a70bf75bb..0d594822e05 100644
--- a/homeassistant/components/mqtt_statestream/__init__.py
+++ b/homeassistant/components/mqtt_statestream/__init__.py
@@ -16,7 +16,6 @@ CONF_BASE_TOPIC = 'base_topic'
CONF_PUBLISH_ATTRIBUTES = 'publish_attributes'
CONF_PUBLISH_TIMESTAMPS = 'publish_timestamps'
-DEPENDENCIES = ['mqtt']
DOMAIN = 'mqtt_statestream'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/mqtt_statestream/manifest.json b/homeassistant/components/mqtt_statestream/manifest.json
new file mode 100644
index 00000000000..5fa99363729
--- /dev/null
+++ b/homeassistant/components/mqtt_statestream/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mqtt_statestream",
+ "name": "Mqtt statestream",
+ "documentation": "https://www.home-assistant.io/components/mqtt_statestream",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mvglive/manifest.json b/homeassistant/components/mvglive/manifest.json
new file mode 100644
index 00000000000..5626e244484
--- /dev/null
+++ b/homeassistant/components/mvglive/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mvglive",
+ "name": "Mvglive",
+ "documentation": "https://www.home-assistant.io/components/mvglive",
+ "requirements": [
+ "PyMVGLive==1.1.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py
index 71690f643f4..8c887031aa9 100644
--- a/homeassistant/components/mvglive/sensor.py
+++ b/homeassistant/components/mvglive/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for real-time departure information for public transport in Munich.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.mvglive/
-"""
+"""Support for departure information for public transport in Munich."""
import logging
from datetime import timedelta
@@ -16,8 +11,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME, ATTR_ATTRIBUTION)
-REQUIREMENTS = ['PyMVGLive==1.1.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_NEXT_DEPARTURE = 'nextdeparture'
diff --git a/homeassistant/components/mychevy/__init__.py b/homeassistant/components/mychevy/__init__.py
index e6fd7f19c2a..b4235362ff2 100644
--- a/homeassistant/components/mychevy/__init__.py
+++ b/homeassistant/components/mychevy/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
-REQUIREMENTS = ['mychevy==1.2.0']
-
DOMAIN = 'mychevy'
UPDATE_TOPIC = DOMAIN
ERROR_TOPIC = DOMAIN + "_error"
diff --git a/homeassistant/components/mychevy/manifest.json b/homeassistant/components/mychevy/manifest.json
new file mode 100644
index 00000000000..1ff997372ed
--- /dev/null
+++ b/homeassistant/components/mychevy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mychevy",
+ "name": "Mychevy",
+ "documentation": "https://www.home-assistant.io/components/mychevy",
+ "requirements": [
+ "mychevy==1.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mycroft/__init__.py b/homeassistant/components/mycroft/__init__.py
index 29f6383f686..fdcedfb7345 100644
--- a/homeassistant/components/mycroft/__init__.py
+++ b/homeassistant/components/mycroft/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.const import CONF_HOST
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['mycroftapi==2.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'mycroft'
diff --git a/homeassistant/components/mycroft/manifest.json b/homeassistant/components/mycroft/manifest.json
new file mode 100644
index 00000000000..77e5a524aac
--- /dev/null
+++ b/homeassistant/components/mycroft/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mycroft",
+ "name": "Mycroft",
+ "documentation": "https://www.home-assistant.io/components/mycroft",
+ "requirements": [
+ "mycroftapi==2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mycroft/notify.py b/homeassistant/components/mycroft/notify.py
index a8a401a9c1f..5918f16290d 100644
--- a/homeassistant/components/mycroft/notify.py
+++ b/homeassistant/components/mycroft/notify.py
@@ -1,15 +1,8 @@
-"""
-Mycroft AI notification platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.mycroft/
-"""
+"""Mycroft AI notification platform."""
import logging
from homeassistant.components.notify import BaseNotificationService
-DEPENDENCIES = ['mycroft']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/myq/cover.py b/homeassistant/components/myq/cover.py
index b2587c06512..395e5d4e959 100644
--- a/homeassistant/components/myq/cover.py
+++ b/homeassistant/components/myq/cover.py
@@ -1,21 +1,16 @@
-"""
-Support for MyQ-Enabled Garage Doors.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/cover.myq/
-"""
+"""Support for MyQ-Enabled Garage Doors."""
import logging
-
import voluptuous as vol
from homeassistant.components.cover import (
- PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, CoverDevice)
+ CoverDevice, PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN
+)
from homeassistant.const import (
CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
- STATE_OPEN, STATE_OPENING)
+ STATE_OPEN, STATE_OPENING
+)
from homeassistant.helpers import aiohttp_client, config_validation as cv
-REQUIREMENTS = ['pymyq==1.1.0']
_LOGGER = logging.getLogger(__name__)
MYQ_TO_HASS = {
diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json
new file mode 100644
index 00000000000..c4057fecb25
--- /dev/null
+++ b/homeassistant/components/myq/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "myq",
+ "name": "Myq",
+ "documentation": "https://www.home-assistant.io/components/myq",
+ "requirements": [
+ "pymyq==1.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py
index 7ca21ac582a..12d210b50a3 100644
--- a/homeassistant/components/mysensors/__init__.py
+++ b/homeassistant/components/mysensors/__init__.py
@@ -17,8 +17,6 @@ from .const import (
from .device import get_mysensors_devices
from .gateway import get_mysensors_gateway, setup_gateways, finish_setup
-REQUIREMENTS = ['pymysensors==0.18.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_DEBUG = 'debug'
diff --git a/homeassistant/components/mysensors/manifest.json b/homeassistant/components/mysensors/manifest.json
new file mode 100644
index 00000000000..f18f5d4f8dd
--- /dev/null
+++ b/homeassistant/components/mysensors/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "mysensors",
+ "name": "Mysensors",
+ "documentation": "https://www.home-assistant.io/components/mysensors",
+ "requirements": [
+ "pymysensors==0.18.0"
+ ],
+ "dependencies": [],
+ "after_dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/mysensors/services.yaml b/homeassistant/components/mysensors/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/mystrom/binary_sensor.py b/homeassistant/components/mystrom/binary_sensor.py
index 4927be27eb3..d3b4dd554a9 100644
--- a/homeassistant/components/mystrom/binary_sensor.py
+++ b/homeassistant/components/mystrom/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the myStrom buttons.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.mystrom/
-"""
+"""Support for the myStrom buttons."""
import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
@@ -12,8 +7,6 @@ from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
-
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py
index f9b8dcd203b..149b83b2487 100644
--- a/homeassistant/components/mystrom/light.py
+++ b/homeassistant/components/mystrom/light.py
@@ -10,8 +10,6 @@ from homeassistant.components.light import (
ATTR_HS_COLOR)
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
-REQUIREMENTS = ['python-mystrom==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'myStrom bulb'
diff --git a/homeassistant/components/mystrom/manifest.json b/homeassistant/components/mystrom/manifest.json
new file mode 100644
index 00000000000..0e17f33f72e
--- /dev/null
+++ b/homeassistant/components/mystrom/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "mystrom",
+ "name": "Mystrom",
+ "documentation": "https://www.home-assistant.io/components/mystrom",
+ "requirements": [
+ "python-mystrom==0.5.0"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/mystrom/switch.py b/homeassistant/components/mystrom/switch.py
index a25517eea91..3fbd6957eb9 100644
--- a/homeassistant/components/mystrom/switch.py
+++ b/homeassistant/components/mystrom/switch.py
@@ -7,8 +7,6 @@ from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_HOST)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-mystrom==0.5.0']
-
DEFAULT_NAME = 'myStrom Switch'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/mythicbeastsdns/__init__.py b/homeassistant/components/mythicbeastsdns/__init__.py
index 3d0d250557b..02441d9c650 100644
--- a/homeassistant/components/mythicbeastsdns/__init__.py
+++ b/homeassistant/components/mythicbeastsdns/__init__.py
@@ -1,19 +1,16 @@
"""Support for Mythic Beasts Dynamic DNS service."""
-from datetime import timedelta
import logging
+from datetime import timedelta
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
- CONF_HOST, CONF_DOMAIN, CONF_PASSWORD, CONF_UPDATE_INTERVAL,
- CONF_SCAN_INTERVAL, CONF_UPDATE_INTERVAL_INVALIDATION_VERSION
+ CONF_DOMAIN, CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
-REQUIREMENTS = ['mbddns==0.1.2']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'mythicbeastsdns'
@@ -21,23 +18,13 @@ DOMAIN = 'mythicbeastsdns'
DEFAULT_INTERVAL = timedelta(minutes=10)
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(
- vol.Schema({
- vol.Required(CONF_DOMAIN): cv.string,
- vol.Required(CONF_HOST): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=DEFAULT_INTERVAL
- )
- )
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_DOMAIN): cv.string,
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
+ vol.All(cv.time_period, cv.positive_timedelta),
+ })
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/mythicbeastsdns/manifest.json b/homeassistant/components/mythicbeastsdns/manifest.json
new file mode 100644
index 00000000000..4e37544a99a
--- /dev/null
+++ b/homeassistant/components/mythicbeastsdns/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "mythicbeastsdns",
+ "name": "Mythicbeastsdns",
+ "documentation": "https://www.home-assistant.io/components/mythicbeastsdns",
+ "requirements": [
+ "mbddns==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/n26/__init__.py b/homeassistant/components/n26/__init__.py
new file mode 100644
index 00000000000..fb7084bffe7
--- /dev/null
+++ b/homeassistant/components/n26/__init__.py
@@ -0,0 +1,151 @@
+"""Support for N26 bank accounts."""
+from datetime import datetime, timedelta, timezone
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME)
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.discovery import load_platform
+from homeassistant.util import Throttle
+
+from .const import DATA, DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+DEFAULT_SCAN_INTERVAL = timedelta(minutes=30)
+
+# define configuration parameters
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL,
+ default=DEFAULT_SCAN_INTERVAL): cv.time_period,
+ }),
+}, extra=vol.ALLOW_EXTRA)
+
+N26_COMPONENTS = [
+ 'sensor',
+ 'switch'
+]
+
+
+def setup(hass, config):
+ """Set up N26 Component."""
+ user = config[DOMAIN][CONF_USERNAME]
+ password = config[DOMAIN][CONF_PASSWORD]
+
+ from n26 import api, config as api_config
+ api = api.Api(api_config.Config(user, password))
+
+ from requests import HTTPError
+ try:
+ api.get_token()
+ except HTTPError as err:
+ _LOGGER.error(str(err))
+ return False
+
+ api_data = N26Data(api)
+ api_data.update()
+
+ hass.data[DOMAIN] = {}
+ hass.data[DOMAIN][DATA] = api_data
+
+ # Load components for supported devices
+ for component in N26_COMPONENTS:
+ load_platform(hass, component, DOMAIN, {}, config)
+
+ return True
+
+
+def timestamp_ms_to_date(epoch_ms) -> datetime or None:
+ """Convert millisecond timestamp to datetime."""
+ if epoch_ms:
+ return datetime.fromtimestamp(epoch_ms / 1000, timezone.utc)
+
+
+class N26Data:
+ """Handle N26 API object and limit updates."""
+
+ def __init__(self, api):
+ """Initialize the data object."""
+ self._api = api
+
+ self._account_info = {}
+ self._balance = {}
+ self._limits = {}
+ self._account_statuses = {}
+
+ self._cards = {}
+ self._spaces = {}
+
+ @property
+ def api(self):
+ """Return N26 api client."""
+ return self._api
+
+ @property
+ def account_info(self):
+ """Return N26 account info."""
+ return self._account_info
+
+ @property
+ def balance(self):
+ """Return N26 account balance."""
+ return self._balance
+
+ @property
+ def limits(self):
+ """Return N26 account limits."""
+ return self._limits
+
+ @property
+ def account_statuses(self):
+ """Return N26 account statuses."""
+ return self._account_statuses
+
+ @property
+ def cards(self):
+ """Return N26 cards."""
+ return self._cards
+
+ def card(self, card_id: str, default: dict = None):
+ """Return a card by its id or the given default."""
+ return next((card for card in self.cards if card["id"] == card_id),
+ default)
+
+ @property
+ def spaces(self):
+ """Return N26 spaces."""
+ return self._spaces
+
+ def space(self, space_id: str, default: dict = None):
+ """Return a space by its id or the given default."""
+ return next((space for space in self.spaces["spaces"]
+ if space["id"] == space_id), default)
+
+ @Throttle(min_time=DEFAULT_SCAN_INTERVAL * 0.8)
+ def update_account(self):
+ """Get the latest account data from N26."""
+ self._account_info = self._api.get_account_info()
+ self._balance = self._api.get_balance()
+ self._limits = self._api.get_account_limits()
+ self._account_statuses = self._api.get_account_statuses()
+
+ @Throttle(min_time=DEFAULT_SCAN_INTERVAL * 0.8)
+ def update_cards(self):
+ """Get the latest cards data from N26."""
+ self._cards = self._api.get_cards()
+
+ @Throttle(min_time=DEFAULT_SCAN_INTERVAL * 0.8)
+ def update_spaces(self):
+ """Get the latest spaces data from N26."""
+ self._spaces = self._api.get_spaces()
+
+ def update(self):
+ """Get the latest data from N26."""
+ self.update_account()
+ self.update_cards()
+ self.update_spaces()
diff --git a/homeassistant/components/n26/const.py b/homeassistant/components/n26/const.py
new file mode 100644
index 00000000000..0a640d0f34e
--- /dev/null
+++ b/homeassistant/components/n26/const.py
@@ -0,0 +1,7 @@
+"""Provides the constants needed for component."""
+DOMAIN = "n26"
+
+DATA = "data"
+
+CARD_STATE_ACTIVE = "M_ACTIVE"
+CARD_STATE_BLOCKED = "M_DISABLED"
diff --git a/homeassistant/components/n26/manifest.json b/homeassistant/components/n26/manifest.json
new file mode 100644
index 00000000000..b49932887d5
--- /dev/null
+++ b/homeassistant/components/n26/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "n26",
+ "name": "N26",
+ "documentation": "https://www.home-assistant.io/components/n26",
+ "requirements": [
+ "n26==0.2.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/n26/sensor.py b/homeassistant/components/n26/sensor.py
new file mode 100644
index 00000000000..be5ad7a1b68
--- /dev/null
+++ b/homeassistant/components/n26/sensor.py
@@ -0,0 +1,246 @@
+"""Support for N26 bank account sensors."""
+import logging
+
+from homeassistant.helpers.entity import Entity
+
+from . import DEFAULT_SCAN_INTERVAL, DOMAIN, timestamp_ms_to_date
+from .const import DATA
+
+_LOGGER = logging.getLogger(__name__)
+
+SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL
+
+ATTR_IBAN = "account"
+ATTR_USABLE_BALANCE = "usable_balance"
+ATTR_BANK_BALANCE = "bank_balance"
+
+ATTR_ACC_OWNER_TITLE = "owner_title"
+ATTR_ACC_OWNER_FIRST_NAME = "owner_first_name"
+ATTR_ACC_OWNER_LAST_NAME = "owner_last_name"
+ATTR_ACC_OWNER_GENDER = "owner_gender"
+ATTR_ACC_OWNER_BIRTH_DATE = "owner_birth_date"
+ATTR_ACC_OWNER_EMAIL = "owner_email"
+ATTR_ACC_OWNER_PHONE_NUMBER = "owner_phone_number"
+
+ICON_ACCOUNT = 'mdi:currency-eur'
+ICON_CARD = 'mdi:credit-card'
+ICON_SPACE = 'mdi:crop-square'
+
+
+def setup_platform(
+ hass, config, add_entities, discovery_info=None):
+ """Set up the N26 sensor platform."""
+ api_data = hass.data[DOMAIN][DATA]
+
+ sensor_entities = [N26Account(api_data)]
+
+ for card in api_data.cards:
+ sensor_entities.append(N26Card(api_data, card))
+
+ for space in api_data.spaces["spaces"]:
+ sensor_entities.append(N26Space(api_data, space))
+
+ add_entities(sensor_entities)
+
+
+class N26Account(Entity):
+ """Sensor for a N26 balance account.
+
+ A balance account contains an amount of money (=balance). The amount may
+ also be negative.
+ """
+
+ def __init__(self, api_data) -> None:
+ """Initialize a N26 balance account."""
+ self._data = api_data
+ self._iban = self._data.balance["iban"]
+
+ def update(self) -> None:
+ """Get the current balance and currency for the account."""
+ self._data.update_account()
+
+ @property
+ def unique_id(self):
+ """Return the unique ID of the entity."""
+ return self._iban[-4:]
+
+ @property
+ def name(self) -> str:
+ """Friendly name of the sensor."""
+ return "n26_{}".format(self._iban[-4:])
+
+ @property
+ def state(self) -> float:
+ """Return the balance of the account as state."""
+ if self._data.balance is None:
+ return None
+
+ return self._data.balance.get("availableBalance")
+
+ @property
+ def unit_of_measurement(self) -> str:
+ """Use the currency as unit of measurement."""
+ if self._data.balance is None:
+ return None
+
+ return self._data.balance.get("currency")
+
+ @property
+ def device_state_attributes(self) -> dict:
+ """Additional attributes of the sensor."""
+ attributes = {
+ ATTR_IBAN: self._data.balance.get("iban"),
+ ATTR_BANK_BALANCE: self._data.balance.get("bankBalance"),
+ ATTR_USABLE_BALANCE: self._data.balance.get("usableBalance"),
+ ATTR_ACC_OWNER_TITLE: self._data.account_info.get("title"),
+ ATTR_ACC_OWNER_FIRST_NAME:
+ self._data.account_info.get("kycFirstName"),
+ ATTR_ACC_OWNER_LAST_NAME:
+ self._data.account_info.get("kycLastName"),
+ ATTR_ACC_OWNER_GENDER: self._data.account_info.get("gender"),
+ ATTR_ACC_OWNER_BIRTH_DATE: timestamp_ms_to_date(
+ self._data.account_info.get("birthDate")),
+ ATTR_ACC_OWNER_EMAIL: self._data.account_info.get("email"),
+ ATTR_ACC_OWNER_PHONE_NUMBER:
+ self._data.account_info.get("mobilePhoneNumber"),
+ }
+
+ for limit in self._data.limits:
+ limit_attr_name = "limit_{}".format(limit["limit"].lower())
+ attributes[limit_attr_name] = limit["amount"]
+
+ return attributes
+
+ @property
+ def icon(self) -> str:
+ """Set the icon for the sensor."""
+ return ICON_ACCOUNT
+
+
+class N26Card(Entity):
+ """Sensor for a N26 card."""
+
+ def __init__(self, api_data, card) -> None:
+ """Initialize a N26 card."""
+ self._data = api_data
+ self._account_name = api_data.balance["iban"][-4:]
+ self._card = card
+
+ def update(self) -> None:
+ """Get the current balance and currency for the account."""
+ self._data.update_cards()
+ self._card = self._data.card(self._card["id"], self._card)
+
+ @property
+ def unique_id(self):
+ """Return the unique ID of the entity."""
+ return self._card["id"]
+
+ @property
+ def name(self) -> str:
+ """Friendly name of the sensor."""
+ return "{}_card_{}".format(
+ self._account_name.lower(), self._card["id"])
+
+ @property
+ def state(self) -> float:
+ """Return the balance of the account as state."""
+ return self._card["status"]
+
+ @property
+ def device_state_attributes(self) -> dict:
+ """Additional attributes of the sensor."""
+ attributes = {
+ "apple_pay_eligible": self._card.get("applePayEligible"),
+ "card_activated": timestamp_ms_to_date(
+ self._card.get("cardActivated")),
+ "card_product": self._card.get("cardProduct"),
+ "card_product_type": self._card.get("cardProductType"),
+ "card_settings_id": self._card.get("cardSettingsId"),
+ "card_Type": self._card.get("cardType"),
+ "design": self._card.get("design"),
+ "exceet_actual_delivery_date":
+ self._card.get("exceetActualDeliveryDate"),
+ "exceet_card_status": self._card.get("exceetCardStatus"),
+ "exceet_expected_delivery_date":
+ self._card.get("exceetExpectedDeliveryDate"),
+ "exceet_express_card_delivery":
+ self._card.get("exceetExpressCardDelivery"),
+ "exceet_express_card_delivery_email_sent":
+ self._card.get("exceetExpressCardDeliveryEmailSent"),
+ "exceet_express_card_delivery_tracking_id":
+ self._card.get("exceetExpressCardDeliveryTrackingId"),
+ "expiration_date": timestamp_ms_to_date(
+ self._card.get("expirationDate")),
+ "google_pay_eligible": self._card.get("googlePayEligible"),
+ "masked_pan": self._card.get("maskedPan"),
+ "membership": self._card.get("membership"),
+ "mpts_card": self._card.get("mptsCard"),
+ "pan": self._card.get("pan"),
+ "pin_defined": timestamp_ms_to_date(self._card.get("pinDefined")),
+ "username_on_card": self._card.get("usernameOnCard"),
+ }
+ return attributes
+
+ @property
+ def icon(self) -> str:
+ """Set the icon for the sensor."""
+ return ICON_CARD
+
+
+class N26Space(Entity):
+ """Sensor for a N26 space."""
+
+ def __init__(self, api_data, space) -> None:
+ """Initialize a N26 space."""
+ self._data = api_data
+ self._space = space
+
+ def update(self) -> None:
+ """Get the current balance and currency for the account."""
+ self._data.update_spaces()
+ self._space = self._data.space(self._space["id"], self._space)
+
+ @property
+ def unique_id(self):
+ """Return the unique ID of the entity."""
+ return "space_{}".format(self._space["name"].lower())
+
+ @property
+ def name(self) -> str:
+ """Friendly name of the sensor."""
+ return self._space["name"]
+
+ @property
+ def state(self) -> float:
+ """Return the balance of the account as state."""
+ return self._space["balance"]["availableBalance"]
+
+ @property
+ def unit_of_measurement(self) -> str:
+ """Use the currency as unit of measurement."""
+ return self._space["balance"]["currency"]
+
+ @property
+ def device_state_attributes(self) -> dict:
+ """Additional attributes of the sensor."""
+ goal_value = ""
+ if "goal" in self._space:
+ goal_value = self._space.get("goal").get("amount")
+
+ attributes = {
+ "name": self._space.get("name"),
+ "goal": goal_value,
+ "background_image_url": self._space.get("backgroundImageUrl"),
+ "image_url": self._space.get("imageUrl"),
+ "is_card_attached": self._space.get("isCardAttached"),
+ "is_hidden_from_balance": self._space.get("isHiddenFromBalance"),
+ "is_locked": self._space.get("isLocked"),
+ "is_primary": self._space.get("isPrimary"),
+ }
+ return attributes
+
+ @property
+ def icon(self) -> str:
+ """Set the icon for the sensor."""
+ return ICON_SPACE
diff --git a/homeassistant/components/n26/switch.py b/homeassistant/components/n26/switch.py
new file mode 100644
index 00000000000..15221255097
--- /dev/null
+++ b/homeassistant/components/n26/switch.py
@@ -0,0 +1,62 @@
+"""Support for N26 switches."""
+import logging
+
+from homeassistant.components.switch import SwitchDevice
+
+from . import DEFAULT_SCAN_INTERVAL, DOMAIN
+from .const import CARD_STATE_ACTIVE, CARD_STATE_BLOCKED, DATA
+
+_LOGGER = logging.getLogger(__name__)
+
+SCAN_INTERVAL = DEFAULT_SCAN_INTERVAL
+
+
+def setup_platform(
+ hass, config, add_entities, discovery_info=None):
+ """Set up the N26 switch platform."""
+ api_data = hass.data[DOMAIN][DATA]
+
+ switch_entities = []
+ for card in api_data.cards:
+ switch_entities.append(N26CardSwitch(api_data, card))
+
+ add_entities(switch_entities)
+
+
+class N26CardSwitch(SwitchDevice):
+ """Representation of a N26 card block/unblock switch."""
+
+ def __init__(self, api_data, card: dict):
+ """Initialize the N26 card block/unblock switch."""
+ self._data = api_data
+ self._card = card
+
+ @property
+ def unique_id(self):
+ """Return the unique ID of the entity."""
+ return self._card["id"]
+
+ @property
+ def name(self) -> str:
+ """Friendly name of the sensor."""
+ return "card_{}".format(self._card["id"])
+
+ @property
+ def is_on(self):
+ """Return true if switch is on."""
+ return self._card["status"] == CARD_STATE_ACTIVE
+
+ def turn_on(self, **kwargs):
+ """Block the card."""
+ self._data.api.unblock_card(self._card["id"])
+ self._card["status"] = CARD_STATE_ACTIVE
+
+ def turn_off(self, **kwargs):
+ """Unblock the card."""
+ self._data.api.block_card(self._card["id"])
+ self._card["status"] = CARD_STATE_BLOCKED
+
+ def update(self):
+ """Update the switch state."""
+ self._data.update_cards()
+ self._card = self._data.card(self._card["id"], self._card)
diff --git a/homeassistant/components/nad/manifest.json b/homeassistant/components/nad/manifest.json
new file mode 100644
index 00000000000..c624acd73da
--- /dev/null
+++ b/homeassistant/components/nad/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nad",
+ "name": "Nad",
+ "documentation": "https://www.home-assistant.io/components/nad",
+ "requirements": [
+ "nad_receiver==0.0.11"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py
index 00738abe4d1..60747fa6398 100644
--- a/homeassistant/components/nad/media_player.py
+++ b/homeassistant/components/nad/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing with NAD receivers through RS-232.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.nad/
-"""
+"""Support for interfacing with NAD receivers through RS-232."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.components.media_player.const import (
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP)
from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON, CONF_HOST
-REQUIREMENTS = ['nad_receiver==0.0.11']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_TYPE = 'RS232'
diff --git a/homeassistant/components/namecheapdns/__init__.py b/homeassistant/components/namecheapdns/__init__.py
index f86e7d18556..d3c48d568bd 100644
--- a/homeassistant/components/namecheapdns/__init__.py
+++ b/homeassistant/components/namecheapdns/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_DOMAIN
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['defusedxml==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'namecheapdns'
diff --git a/homeassistant/components/namecheapdns/manifest.json b/homeassistant/components/namecheapdns/manifest.json
new file mode 100644
index 00000000000..c5c46b92166
--- /dev/null
+++ b/homeassistant/components/namecheapdns/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "namecheapdns",
+ "name": "Namecheapdns",
+ "documentation": "https://www.home-assistant.io/components/namecheapdns",
+ "requirements": [
+ "defusedxml==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py
index 8cb899f1d28..017bd0a256d 100644
--- a/homeassistant/components/nanoleaf/light.py
+++ b/homeassistant/components/nanoleaf/light.py
@@ -1,17 +1,13 @@
-"""
-Support for Nanoleaf Lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.nanoleaf/
-"""
+"""Support for Nanoleaf Lights."""
import logging
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR,
- PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP,
- SUPPORT_EFFECT, Light)
+ ATTR_TRANSITION, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
+ SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT,
+ SUPPORT_TRANSITION, Light)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
import homeassistant.helpers.config_validation as cv
from homeassistant.util import color as color_util
@@ -19,8 +15,6 @@ from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['pynanoleaf==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Nanoleaf'
@@ -32,7 +26,7 @@ CONFIG_FILE = '.nanoleaf.conf'
ICON = 'mdi:triangle-outline'
SUPPORT_NANOLEAF = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT |
- SUPPORT_COLOR)
+ SUPPORT_COLOR | SUPPORT_TRANSITION)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
@@ -171,27 +165,40 @@ class NanoleafLight(Light):
def turn_on(self, **kwargs):
"""Instruct the light to turn on."""
- self._light.on = True
brightness = kwargs.get(ATTR_BRIGHTNESS)
hs_color = kwargs.get(ATTR_HS_COLOR)
color_temp_mired = kwargs.get(ATTR_COLOR_TEMP)
effect = kwargs.get(ATTR_EFFECT)
+ transition = kwargs.get(ATTR_TRANSITION)
if hs_color:
hue, saturation = hs_color
self._light.hue = int(hue)
self._light.saturation = int(saturation)
-
if color_temp_mired:
self._light.color_temperature = mired_to_kelvin(color_temp_mired)
- if brightness:
- self._light.brightness = int(brightness / 2.55)
+
+ if transition:
+ if brightness: # tune to the required brightness in n seconds
+ self._light.brightness_transition(
+ int(brightness / 2.55), int(transition))
+ else: # If brightness is not specified, assume full brightness
+ self._light.brightness_transition(100, int(transition))
+ else: # If no transition is occurring, turn on the light
+ self._light.on = True
+ if brightness:
+ self._light.brightness = int(brightness / 2.55)
+
if effect:
self._light.effect = effect
def turn_off(self, **kwargs):
"""Instruct the light to turn off."""
- self._light.on = False
+ transition = kwargs.get(ATTR_TRANSITION)
+ if transition:
+ self._light.brightness_transition(0, int(transition))
+ else:
+ self._light.on = False
def update(self):
"""Fetch new state data for this light."""
diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json
new file mode 100644
index 00000000000..a59a6352af2
--- /dev/null
+++ b/homeassistant/components/nanoleaf/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nanoleaf",
+ "name": "Nanoleaf",
+ "documentation": "https://www.home-assistant.io/components/nanoleaf",
+ "requirements": [
+ "pynanoleaf==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py
index bb717b8d230..f179248b563 100644
--- a/homeassistant/components/neato/__init__.py
+++ b/homeassistant/components/neato/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
-REQUIREMENTS = ['pybotvac==0.0.13']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'neato'
diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py
index f8106c3e645..5d38e7b7880 100644
--- a/homeassistant/components/neato/camera.py
+++ b/homeassistant/components/neato/camera.py
@@ -8,8 +8,6 @@ from . import NEATO_LOGIN, NEATO_MAP_DATA, NEATO_ROBOTS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['neato']
-
SCAN_INTERVAL = timedelta(minutes=10)
diff --git a/homeassistant/components/neato/manifest.json b/homeassistant/components/neato/manifest.json
new file mode 100644
index 00000000000..042d7dcef09
--- /dev/null
+++ b/homeassistant/components/neato/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "neato",
+ "name": "Neato",
+ "documentation": "https://www.home-assistant.io/components/neato",
+ "requirements": [
+ "pybotvac==0.0.13"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/neato/services.yaml b/homeassistant/components/neato/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py
index ea60f9492e2..0721381a563 100644
--- a/homeassistant/components/neato/switch.py
+++ b/homeassistant/components/neato/switch.py
@@ -11,8 +11,6 @@ from . import NEATO_LOGIN, NEATO_ROBOTS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['neato']
-
SCAN_INTERVAL = timedelta(minutes=10)
SWITCH_TYPE_SCHEDULE = 'schedule'
diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py
index 3575301ea97..061d8fd04c8 100644
--- a/homeassistant/components/neato/vacuum.py
+++ b/homeassistant/components/neato/vacuum.py
@@ -21,8 +21,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['neato']
-
SCAN_INTERVAL = timedelta(minutes=5)
SUPPORT_NEATO = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \
diff --git a/homeassistant/components/nederlandse_spoorwegen/manifest.json b/homeassistant/components/nederlandse_spoorwegen/manifest.json
new file mode 100644
index 00000000000..baa6551cc7c
--- /dev/null
+++ b/homeassistant/components/nederlandse_spoorwegen/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nederlandse_spoorwegen",
+ "name": "Nederlandse spoorwegen",
+ "documentation": "https://www.home-assistant.io/components/nederlandse_spoorwegen",
+ "requirements": [
+ "nsapi==2.7.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py
index 5d9376ad9eb..7fc3e438f38 100644
--- a/homeassistant/components/nederlandse_spoorwegen/sensor.py
+++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Nederlandse Spoorwegen public transport.
-
-For more details on this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.nederlandse_spoorwegen/
-"""
+"""Support for Nederlandse Spoorwegen public transport."""
from datetime import datetime, timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['nsapi==2.7.4']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by NS"
diff --git a/homeassistant/components/nello/lock.py b/homeassistant/components/nello/lock.py
index e7eaea8fcd3..124fa6769ec 100644
--- a/homeassistant/components/nello/lock.py
+++ b/homeassistant/components/nello/lock.py
@@ -1,9 +1,4 @@
-"""
-Nello.io lock platform.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/lock.nello/
-"""
+"""Nello.io lock platform."""
from itertools import filterfalse
import logging
@@ -13,8 +8,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.lock import (LockDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME)
-REQUIREMENTS = ['pynello==2.0.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
diff --git a/homeassistant/components/nello/manifest.json b/homeassistant/components/nello/manifest.json
new file mode 100644
index 00000000000..0caafd7e27a
--- /dev/null
+++ b/homeassistant/components/nello/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "nello",
+ "name": "Nello",
+ "documentation": "https://www.home-assistant.io/components/nello",
+ "requirements": [
+ "pynello==2.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@pschmitt"
+ ]
+}
diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py
index 97896f9aa3f..8d9d081e6d8 100644
--- a/homeassistant/components/ness_alarm/__init__.py
+++ b/homeassistant/components/ness_alarm/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['nessclient==0.9.15']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'ness_alarm'
diff --git a/homeassistant/components/ness_alarm/alarm_control_panel.py b/homeassistant/components/ness_alarm/alarm_control_panel.py
index f77b534980f..06a3f9f1e13 100644
--- a/homeassistant/components/ness_alarm/alarm_control_panel.py
+++ b/homeassistant/components/ness_alarm/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for Ness D8X/D16X alarm panel.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.ness_alarm/
-"""
+"""Support for Ness D8X/D16X alarm panel."""
import logging
@@ -18,8 +13,6 @@ from . import DATA_NESS, SIGNAL_ARMING_STATE_CHANGED
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['ness_alarm']
-
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/ness_alarm/binary_sensor.py b/homeassistant/components/ness_alarm/binary_sensor.py
index 7b684f74aa1..6d9486577a7 100644
--- a/homeassistant/components/ness_alarm/binary_sensor.py
+++ b/homeassistant/components/ness_alarm/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Ness D8X/D16X zone states - represented as binary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.ness_alarm/
-"""
+"""Support for Ness D8X/D16X zone states - represented as binary sensors."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -14,7 +9,6 @@ from . import (
CONF_ZONE_ID, CONF_ZONE_NAME, CONF_ZONE_TYPE, CONF_ZONES,
SIGNAL_ZONE_CHANGED, ZoneChangedData)
-DEPENDENCIES = ['ness_alarm']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/ness_alarm/manifest.json b/homeassistant/components/ness_alarm/manifest.json
new file mode 100644
index 00000000000..93b19470ac4
--- /dev/null
+++ b/homeassistant/components/ness_alarm/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ness_alarm",
+ "name": "Ness alarm",
+ "documentation": "https://www.home-assistant.io/components/ness_alarm",
+ "requirements": [
+ "nessclient==0.9.15"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@nickw444"
+ ]
+}
diff --git a/homeassistant/components/ness_alarm/services.yaml b/homeassistant/components/ness_alarm/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/nest/.translations/ca.json b/homeassistant/components/nest/.translations/ca.json
index 179c8f20951..b242208791b 100644
--- a/homeassistant/components/nest/.translations/ca.json
+++ b/homeassistant/components/nest/.translations/ca.json
@@ -17,7 +17,7 @@
"data": {
"flow_impl": "Prove\u00efdor"
},
- "description": "Tria a amb quin prove\u00efdor d'autenticaci\u00f3 vols autenticar-te amb Nest.",
+ "description": "Tria quin prove\u00efdor d'autenticaci\u00f3 vols utilitzar per autenticar-te amb Nest.",
"title": "Prove\u00efdor d'autenticaci\u00f3"
},
"link": {
diff --git a/homeassistant/components/nest/.translations/ko.json b/homeassistant/components/nest/.translations/ko.json
index a53a26bca5a..42170910d14 100644
--- a/homeassistant/components/nest/.translations/ko.json
+++ b/homeassistant/components/nest/.translations/ko.json
@@ -4,7 +4,7 @@
"already_setup": "\ud558\ub098\uc758 Nest \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.",
"authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
"authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
- "no_flows": "Nest \ub97c \uc778\uc99d\ud558\uae30 \uc804\uc5d0 Nest \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/nest/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694."
+ "no_flows": "Nest \ub97c \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Nest \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/nest/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694."
},
"error": {
"internal_error": "\ucf54\ub4dc \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \ub0b4\ubd80 \uc624\ub958 \ubc1c\uc0dd",
@@ -17,7 +17,7 @@
"data": {
"flow_impl": "\uacf5\uae09\uc790"
},
- "description": "Nest\ub85c \uc778\uc99d\ud558\ub824\ub294 \uc778\uc99d \uacf5\uae09\uc790\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.",
+ "description": "Nest \ub97c \uc778\uc99d\ud558\uae30 \uc704\ud55c \uc778\uc99d \uacf5\uae09\uc790\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.",
"title": "\uc778\uc99d \uacf5\uae09\uc790"
},
"link": {
diff --git a/homeassistant/components/nest/.translations/ru.json b/homeassistant/components/nest/.translations/ru.json
index ff86c34ac71..1c24acd96e4 100644
--- a/homeassistant/components/nest/.translations/ru.json
+++ b/homeassistant/components/nest/.translations/ru.json
@@ -4,7 +4,7 @@
"already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
"authorize_url_fail": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
"authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
- "no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Nest \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/nest/)."
+ "no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Nest \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/nest/)."
},
"error": {
"internal_error": "\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u043e\u0434\u0430",
@@ -17,7 +17,7 @@
"data": {
"flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440"
},
- "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0432\u0445\u043e\u0434 \u0432 Nest.",
+ "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0432\u0445\u043e\u0434.",
"title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438"
},
"link": {
diff --git a/homeassistant/components/nest/.translations/th.json b/homeassistant/components/nest/.translations/th.json
new file mode 100644
index 00000000000..82ec7f168fa
--- /dev/null
+++ b/homeassistant/components/nest/.translations/th.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "error": {
+ "invalid_code": "\u0e23\u0e2b\u0e31\u0e2a\u0e44\u0e21\u0e48\u0e16\u0e39\u0e01\u0e15\u0e49\u0e2d\u0e07"
+ },
+ "step": {
+ "link": {
+ "data": {
+ "code": "Pin code"
+ }
+ }
+ },
+ "title": "Nest"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py
index 21aaa2109a1..cc726cdf175 100644
--- a/homeassistant/components/nest/__init__.py
+++ b/homeassistant/components/nest/__init__.py
@@ -22,8 +22,6 @@ from homeassistant.helpers.entity import Entity
from .const import DOMAIN
from . import local_auth
-REQUIREMENTS = ['python-nest==4.1.0']
-
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/nest/binary_sensor.py b/homeassistant/components/nest/binary_sensor.py
index aa56bfbf29d..1fc8aa8929f 100644
--- a/homeassistant/components/nest/binary_sensor.py
+++ b/homeassistant/components/nest/binary_sensor.py
@@ -10,8 +10,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['nest']
-
BINARY_TYPES = {'online': 'connectivity'}
CLIMATE_BINARY_TYPES = {
diff --git a/homeassistant/components/nest/camera.py b/homeassistant/components/nest/camera.py
index 8b450e02b46..029de178f24 100644
--- a/homeassistant/components/nest/camera.py
+++ b/homeassistant/components/nest/camera.py
@@ -11,8 +11,6 @@ from homeassistant.util.dt import utcnow
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['nest']
-
NEST_BRAND = 'Nest'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py
index cd9a7cb71b6..4707d8d0f8c 100644
--- a/homeassistant/components/nest/climate.py
+++ b/homeassistant/components/nest/climate.py
@@ -16,7 +16,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import DATA_NEST, DOMAIN as NEST_DOMAIN, SIGNAL_NEST_UPDATE
-DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json
new file mode 100644
index 00000000000..9f2e4202f93
--- /dev/null
+++ b/homeassistant/components/nest/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "nest",
+ "name": "Nest",
+ "documentation": "https://www.home-assistant.io/components/nest",
+ "requirements": [
+ "python-nest==4.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@awarecan"
+ ]
+}
diff --git a/homeassistant/components/nest/sensor.py b/homeassistant/components/nest/sensor.py
index ecae83e303c..2bfeea89784 100644
--- a/homeassistant/components/nest/sensor.py
+++ b/homeassistant/components/nest/sensor.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
from . import CONF_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice
-DEPENDENCIES = ['nest']
-
SENSOR_TYPES = ['humidity', 'operation_mode', 'hvac_state']
TEMP_SENSOR_TYPES = ['temperature', 'target']
diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py
index 2036e55b3a8..cf64363ba50 100644
--- a/homeassistant/components/netatmo/__init__.py
+++ b/homeassistant/components/netatmo/__init__.py
@@ -12,9 +12,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyatmo==1.9']
-DEPENDENCIES = ['webhook']
-
_LOGGER = logging.getLogger(__name__)
DATA_PERSONS = 'netatmo_persons'
diff --git a/homeassistant/components/netatmo/binary_sensor.py b/homeassistant/components/netatmo/binary_sensor.py
index a11ce6bddf7..f282faf82c8 100644
--- a/homeassistant/components/netatmo/binary_sensor.py
+++ b/homeassistant/components/netatmo/binary_sensor.py
@@ -8,12 +8,10 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import CONF_TIMEOUT
from homeassistant.helpers import config_validation as cv
-from . import CameraData
+from . import CameraData, NETATMO_AUTH
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['netatmo']
-
# These are the available sensors mapped to binary_sensor class
WELCOME_SENSOR_TYPES = {
"Someone known": "motion",
@@ -53,7 +51,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the access to Netatmo binary sensor."""
- netatmo = hass.components.netatmo
home = config.get(CONF_HOME)
timeout = config.get(CONF_TIMEOUT)
if timeout is None:
@@ -63,7 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
import pyatmo
try:
- data = CameraData(hass, netatmo.NETATMO_AUTH, home)
+ data = CameraData(hass, NETATMO_AUTH, home)
if not data.get_camera_names():
return None
except pyatmo.NoDevice:
diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py
index 513852cde23..b74dce4b262 100644
--- a/homeassistant/components/netatmo/camera.py
+++ b/homeassistant/components/netatmo/camera.py
@@ -4,35 +4,41 @@ import logging
import requests
import voluptuous as vol
-from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
+from homeassistant.components.camera import (
+ PLATFORM_SCHEMA, Camera, SUPPORT_STREAM)
from homeassistant.const import CONF_VERIFY_SSL
from homeassistant.helpers import config_validation as cv
-from . import CameraData
-
-DEPENDENCIES = ['netatmo']
+from . import CameraData, NETATMO_AUTH
_LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
+CONF_QUALITY = 'quality'
+
+DEFAULT_QUALITY = 'high'
+
+VALID_QUALITIES = ['high', 'medium', 'low', 'poor']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
+ vol.Optional(CONF_QUALITY, default=DEFAULT_QUALITY):
+ vol.All(cv.string, vol.In(VALID_QUALITIES)),
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up access to Netatmo cameras."""
- netatmo = hass.components.netatmo
home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True)
+ quality = config.get(CONF_QUALITY, DEFAULT_QUALITY)
import pyatmo
try:
- data = CameraData(hass, netatmo.NETATMO_AUTH, home)
+ data = CameraData(hass, NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
camera_type = data.get_camera_type(camera=camera_name, home=home)
if CONF_CAMERAS in config:
@@ -40,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
camera_name not in config[CONF_CAMERAS]:
continue
add_entities([NetatmoCamera(data, camera_name, home,
- camera_type, verify_ssl)])
+ camera_type, verify_ssl, quality)])
data.get_persons()
except pyatmo.NoDevice:
return None
@@ -49,12 +55,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class NetatmoCamera(Camera):
"""Representation of the images published from a Netatmo camera."""
- def __init__(self, data, camera_name, home, camera_type, verify_ssl):
+ def __init__(self, data, camera_name, home, camera_type, verify_ssl,
+ quality):
"""Set up for access to the Netatmo camera images."""
super(NetatmoCamera, self).__init__()
self._data = data
self._camera_name = camera_name
self._verify_ssl = verify_ssl
+ self._quality = quality
if home:
self._name = home + ' / ' + camera_name
else:
@@ -105,3 +113,16 @@ class NetatmoCamera(Camera):
if self._cameratype == "NACamera":
return "Welcome"
return None
+
+ @property
+ def supported_features(self):
+ """Return supported features."""
+ return SUPPORT_STREAM
+
+ @property
+ def stream_source(self):
+ """Return the stream source."""
+ url = '{0}/live/files/{1}/index.m3u8'
+ if self._localurl:
+ return url.format(self._localurl, self._quality)
+ return url.format(self._vpnurl, self._quality)
diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py
index d0537c5912b..00c08c654ef 100644
--- a/homeassistant/components/netatmo/climate.py
+++ b/homeassistant/components/netatmo/climate.py
@@ -14,7 +14,7 @@ from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_NAME)
from homeassistant.util import Throttle
-DEPENDENCIES = ['netatmo']
+from . import NETATMO_AUTH
_LOGGER = logging.getLogger(__name__)
@@ -66,12 +66,10 @@ NA_VALVE = 'NRV'
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the NetAtmo Thermostat."""
- netatmo = hass.components.netatmo
-
import pyatmo
homes_conf = config.get(CONF_HOMES)
try:
- home_data = HomeData(netatmo.NETATMO_AUTH)
+ home_data = HomeData(NETATMO_AUTH)
except pyatmo.NoDevice:
return
@@ -90,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
for home in homes:
_LOGGER.debug("Setting up %s ...", home)
try:
- room_data = ThermostatData(netatmo.NETATMO_AUTH, home)
+ room_data = ThermostatData(NETATMO_AUTH, home)
except pyatmo.NoDevice:
continue
for room_id in room_data.get_room_ids():
diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json
new file mode 100644
index 00000000000..c7e91d645fc
--- /dev/null
+++ b/homeassistant/components/netatmo/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "netatmo",
+ "name": "Netatmo",
+ "documentation": "https://www.home-assistant.io/components/netatmo",
+ "requirements": [
+ "pyatmo==1.10"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py
index 307b76ca434..c9c1101c2a2 100644
--- a/homeassistant/components/netatmo/sensor.py
+++ b/homeassistant/components/netatmo/sensor.py
@@ -12,13 +12,13 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
+from . import NETATMO_AUTH
+
_LOGGER = logging.getLogger(__name__)
CONF_MODULES = 'modules'
CONF_STATION = 'station'
-DEPENDENCIES = ['netatmo']
-
# This is the NetAtmo data upload interval in seconds
NETATMO_UPDATE_INTERVAL = 600
@@ -67,26 +67,24 @@ MODULE_TYPE_INDOOR = 'NAModule4'
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available Netatmo weather sensors."""
- netatmo = hass.components.netatmo
-
dev = []
if CONF_MODULES in config:
- manual_config(netatmo, config, dev)
+ manual_config(config, dev)
else:
- auto_config(netatmo, config, dev)
+ auto_config(config, dev)
if dev:
add_entities(dev, True)
-def manual_config(netatmo, config, dev):
+def manual_config(config, dev):
"""Handle manual configuration."""
import pyatmo
all_classes = all_product_classes()
not_handled = {}
for data_class in all_classes:
- data = NetAtmoData(netatmo.NETATMO_AUTH, data_class,
+ data = NetAtmoData(NETATMO_AUTH, data_class,
config.get(CONF_STATION))
try:
# Iterate each module
@@ -109,13 +107,12 @@ def manual_config(netatmo, config, dev):
_LOGGER.error('Module name: "%s" not found', module_name)
-def auto_config(netatmo, config, dev):
+def auto_config(config, dev):
"""Handle auto configuration."""
import pyatmo
for data_class in all_product_classes():
- data = NetAtmoData(netatmo.NETATMO_AUTH, data_class,
- config.get(CONF_STATION))
+ data = NetAtmoData(NETATMO_AUTH, data_class, config.get(CONF_STATION))
try:
for module_name in data.get_module_names():
for variable in \
diff --git a/homeassistant/components/netatmo_public/manifest.json b/homeassistant/components/netatmo_public/manifest.json
new file mode 100644
index 00000000000..1070f27b33c
--- /dev/null
+++ b/homeassistant/components/netatmo_public/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "netatmo_public",
+ "name": "Netatmo public",
+ "documentation": "https://www.home-assistant.io/components/netatmo_public",
+ "requirements": [],
+ "dependencies": [
+ "netatmo"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/netatmo_public/sensor.py b/homeassistant/components/netatmo_public/sensor.py
index 7a500b66183..814675ca8b7 100644
--- a/homeassistant/components/netatmo_public/sensor.py
+++ b/homeassistant/components/netatmo_public/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Sensors using public Netatmo data.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.netatmo_public/.
-"""
+"""Support for Sensors using public Netatmo data."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['netatmo']
-
CONF_AREAS = 'areas'
CONF_LAT_NE = 'lat_ne'
CONF_LON_NE = 'lon_ne'
diff --git a/homeassistant/components/netdata/manifest.json b/homeassistant/components/netdata/manifest.json
new file mode 100644
index 00000000000..9c3b8ad33d2
--- /dev/null
+++ b/homeassistant/components/netdata/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "netdata",
+ "name": "Netdata",
+ "documentation": "https://www.home-assistant.io/components/netdata",
+ "requirements": [
+ "netdata==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/netdata/sensor.py b/homeassistant/components/netdata/sensor.py
index 6a6eea02005..eb6d6088ea8 100644
--- a/homeassistant/components/netdata/sensor.py
+++ b/homeassistant/components/netdata/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support gathering system information of hosts which are running netdata.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.netdata/
-"""
+"""Support gathering system information of hosts which are running netdata."""
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['netdata==0.1.2']
-
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
diff --git a/homeassistant/components/netgear/device_tracker.py b/homeassistant/components/netgear/device_tracker.py
index 49bce932159..36921601cc2 100644
--- a/homeassistant/components/netgear/device_tracker.py
+++ b/homeassistant/components/netgear/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Netgear routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.netgear/
-"""
+"""Support for Netgear routers."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL,
CONF_DEVICES, CONF_EXCLUDE)
-REQUIREMENTS = ['pynetgear==0.5.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_APS = 'accesspoints'
diff --git a/homeassistant/components/netgear/manifest.json b/homeassistant/components/netgear/manifest.json
new file mode 100644
index 00000000000..8fbf185c6af
--- /dev/null
+++ b/homeassistant/components/netgear/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "netgear",
+ "name": "Netgear",
+ "documentation": "https://www.home-assistant.io/components/netgear",
+ "requirements": [
+ "pynetgear==0.5.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py
index a259a361be4..5491fffe969 100644
--- a/homeassistant/components/netgear_lte/__init__.py
+++ b/homeassistant/components/netgear_lte/__init__.py
@@ -11,17 +11,19 @@ from homeassistant.const import (
CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PASSWORD,
CONF_RECIPIENT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
+from homeassistant.components.binary_sensor import (
+ DOMAIN as BINARY_SENSOR_DOMAIN)
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.aiohttp_client import async_create_clientsession
-from homeassistant.helpers.dispatcher import async_dispatcher_send
+from homeassistant.helpers.dispatcher import (
+ async_dispatcher_send, async_dispatcher_connect)
+from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
from . import sensor_types
-REQUIREMENTS = ['eternalegypt==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
@@ -47,8 +49,15 @@ NOTIFY_SCHEMA = vol.Schema({
})
SENSOR_SCHEMA = vol.Schema({
- vol.Optional(CONF_MONITORED_CONDITIONS, default=sensor_types.DEFAULT):
- vol.All(cv.ensure_list, [vol.In(sensor_types.ALL)]),
+ vol.Optional(CONF_MONITORED_CONDITIONS,
+ default=sensor_types.DEFAULT_SENSORS):
+ vol.All(cv.ensure_list, [vol.In(sensor_types.ALL_SENSORS)]),
+})
+
+BINARY_SENSOR_SCHEMA = vol.Schema({
+ vol.Optional(CONF_MONITORED_CONDITIONS,
+ default=sensor_types.DEFAULT_BINARY_SENSORS):
+ vol.All(cv.ensure_list, [vol.In(sensor_types.ALL_BINARY_SENSORS)]),
})
CONFIG_SCHEMA = vol.Schema({
@@ -59,6 +68,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.All(cv.ensure_list, [NOTIFY_SCHEMA]),
vol.Optional(SENSOR_DOMAIN, default={}):
SENSOR_SCHEMA,
+ vol.Optional(BINARY_SENSOR_DOMAIN, default={}):
+ BINARY_SENSOR_SCHEMA,
})])
}, extra=vol.ALLOW_EXTRA)
@@ -160,6 +171,15 @@ async def async_setup(hass, config):
hass.async_create_task(discovery.async_load_platform(
hass, SENSOR_DOMAIN, DOMAIN, discovery_info, config))
+ # Binary Sensor
+ binary_sensor_conf = lte_conf.get(BINARY_SENSOR_DOMAIN)
+ discovery_info = {
+ CONF_HOST: lte_conf[CONF_HOST],
+ BINARY_SENSOR_DOMAIN: binary_sensor_conf,
+ }
+ hass.async_create_task(discovery.async_load_platform(
+ hass, BINARY_SENSOR_DOMAIN, DOMAIN, discovery_info, config))
+
return True
@@ -239,3 +259,48 @@ async def _retry_login(hass, modem_data, password):
await _login(hass, modem_data, password)
except eternalegypt.Error:
delay = min(2*delay, 300)
+
+
+@attr.s
+class LTEEntity(Entity):
+ """Base LTE entity."""
+
+ modem_data = attr.ib()
+ sensor_type = attr.ib()
+
+ _unique_id = attr.ib(init=False)
+
+ @_unique_id.default
+ def _init_unique_id(self):
+ """Register unique_id while we know data is valid."""
+ return "{}_{}".format(
+ self.sensor_type, self.modem_data.data.serial_number)
+
+ async def async_added_to_hass(self):
+ """Register callback."""
+ async_dispatcher_connect(
+ self.hass, DISPATCHER_NETGEAR_LTE, self.async_write_ha_state)
+
+ async def async_update(self):
+ """Force update of state."""
+ await self.modem_data.async_update()
+
+ @property
+ def should_poll(self):
+ """Return that the sensor should not be polled."""
+ return False
+
+ @property
+ def available(self):
+ """Return the availability of the sensor."""
+ return self.modem_data.data is not None
+
+ @property
+ def unique_id(self):
+ """Return a unique ID like 'usage_5TG365AB0078V'."""
+ return self._unique_id
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return "Netgear LTE {}".format(self.sensor_type)
diff --git a/homeassistant/components/netgear_lte/binary_sensor.py b/homeassistant/components/netgear_lte/binary_sensor.py
new file mode 100644
index 00000000000..b13e1b0bbb4
--- /dev/null
+++ b/homeassistant/components/netgear_lte/binary_sensor.py
@@ -0,0 +1,45 @@
+"""Support for Netgear LTE binary sensors."""
+import logging
+
+from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
+from homeassistant.exceptions import PlatformNotReady
+
+from . import CONF_MONITORED_CONDITIONS, DATA_KEY, LTEEntity
+from .sensor_types import BINARY_SENSOR_CLASSES
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info):
+ """Set up Netgear LTE binary sensor devices."""
+ if discovery_info is None:
+ return
+
+ modem_data = hass.data[DATA_KEY].get_modem_data(discovery_info)
+
+ if not modem_data or not modem_data.data:
+ raise PlatformNotReady
+
+ binary_sensor_conf = discovery_info[DOMAIN]
+ monitored_conditions = binary_sensor_conf[CONF_MONITORED_CONDITIONS]
+
+ binary_sensors = []
+ for sensor_type in monitored_conditions:
+ binary_sensors.append(LTEBinarySensor(modem_data, sensor_type))
+
+ async_add_entities(binary_sensors)
+
+
+class LTEBinarySensor(LTEEntity, BinarySensorDevice):
+ """Netgear LTE binary sensor entity."""
+
+ @property
+ def is_on(self):
+ """Return true if the binary sensor is on."""
+ return getattr(self.modem_data.data, self.sensor_type)
+
+ @property
+ def device_class(self):
+ """Return the class of binary sensor."""
+ return BINARY_SENSOR_CLASSES[self.sensor_type]
diff --git a/homeassistant/components/netgear_lte/manifest.json b/homeassistant/components/netgear_lte/manifest.json
new file mode 100644
index 00000000000..1ec50755d04
--- /dev/null
+++ b/homeassistant/components/netgear_lte/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "netgear_lte",
+ "name": "Netgear lte",
+ "documentation": "https://www.home-assistant.io/components/netgear_lte",
+ "requirements": [
+ "eternalegypt==0.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/netgear_lte/notify.py b/homeassistant/components/netgear_lte/notify.py
index fba1a335ace..cb71a7945e3 100644
--- a/homeassistant/components/netgear_lte/notify.py
+++ b/homeassistant/components/netgear_lte/notify.py
@@ -8,8 +8,6 @@ from homeassistant.components.notify import (
from . import CONF_RECIPIENT, DATA_KEY
-DEPENDENCIES = ['netgear_lte']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py
index 1be960edfe3..edf55480a68 100644
--- a/homeassistant/components/netgear_lte/sensor.py
+++ b/homeassistant/components/netgear_lte/sensor.py
@@ -1,17 +1,12 @@
"""Support for Netgear LTE sensors."""
import logging
-import attr
-
from homeassistant.components.sensor import DOMAIN
from homeassistant.exceptions import PlatformNotReady
-from homeassistant.helpers.entity import Entity
-from homeassistant.helpers.dispatcher import async_dispatcher_connect
-from . import CONF_MONITORED_CONDITIONS, DATA_KEY, DISPATCHER_NETGEAR_LTE
-from .sensor_types import SENSOR_SMS, SENSOR_USAGE
-
-DEPENDENCIES = ['netgear_lte']
+from . import CONF_MONITORED_CONDITIONS, DATA_KEY, LTEEntity
+from .sensor_types import (
+ SENSOR_SMS, SENSOR_SMS_TOTAL, SENSOR_USAGE, SENSOR_UNITS)
_LOGGER = logging.getLogger(__name__)
@@ -33,81 +28,57 @@ async def async_setup_platform(
sensors = []
for sensor_type in monitored_conditions:
if sensor_type == SENSOR_SMS:
- sensors.append(SMSSensor(modem_data, sensor_type))
+ sensors.append(SMSUnreadSensor(modem_data, sensor_type))
+ elif sensor_type == SENSOR_SMS_TOTAL:
+ sensors.append(SMSTotalSensor(modem_data, sensor_type))
elif sensor_type == SENSOR_USAGE:
sensors.append(UsageSensor(modem_data, sensor_type))
+ else:
+ sensors.append(GenericSensor(modem_data, sensor_type))
async_add_entities(sensors)
-@attr.s
-class LTESensor(Entity):
+class LTESensor(LTEEntity):
"""Base LTE sensor entity."""
- modem_data = attr.ib()
- sensor_type = attr.ib()
-
- _unique_id = attr.ib(init=False)
-
- @_unique_id.default
- def _init_unique_id(self):
- """Register unique_id while we know data is valid."""
- return "{}_{}".format(
- self.sensor_type, self.modem_data.data.serial_number)
-
- async def async_added_to_hass(self):
- """Register callback."""
- async_dispatcher_connect(
- self.hass, DISPATCHER_NETGEAR_LTE, self.async_write_ha_state)
-
- async def async_update(self):
- """Force update of state."""
- await self.modem_data.async_update()
-
@property
- def should_poll(self):
- """Return that the sensor should not be polled."""
- return False
-
- @property
- def available(self):
- """Return the availability of the sensor."""
- return self.modem_data.data is not None
-
- @property
- def unique_id(self):
- """Return a unique ID like 'usage_5TG365AB0078V'."""
- return self._unique_id
+ def unit_of_measurement(self):
+ """Return the unit of measurement."""
+ return SENSOR_UNITS[self.sensor_type]
-class SMSSensor(LTESensor):
+class SMSUnreadSensor(LTESensor):
"""Unread SMS sensor entity."""
- @property
- def name(self):
- """Return the name of the sensor."""
- return "Netgear LTE SMS"
-
@property
def state(self):
"""Return the state of the sensor."""
return sum(1 for x in self.modem_data.data.sms if x.unread)
+class SMSTotalSensor(LTESensor):
+ """Total SMS sensor entity."""
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return len(self.modem_data.data.sms)
+
+
class UsageSensor(LTESensor):
"""Data usage sensor entity."""
- @property
- def unit_of_measurement(self):
- """Return the unit of measurement."""
- return "MiB"
-
- @property
- def name(self):
- """Return the name of the sensor."""
- return "Netgear LTE usage"
-
@property
def state(self):
"""Return the state of the sensor."""
return round(self.modem_data.data.usage / 1024**2, 1)
+
+
+class GenericSensor(LTESensor):
+ """Sensor entity with raw state."""
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return getattr(self.modem_data.data, self.sensor_type)
diff --git a/homeassistant/components/netgear_lte/sensor_types.py b/homeassistant/components/netgear_lte/sensor_types.py
index b05ecf6074a..3171c1d9663 100644
--- a/homeassistant/components/netgear_lte/sensor_types.py
+++ b/homeassistant/components/netgear_lte/sensor_types.py
@@ -1,8 +1,37 @@
"""Define possible sensor types."""
+from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY
+
SENSOR_SMS = 'sms'
+SENSOR_SMS_TOTAL = 'sms_total'
SENSOR_USAGE = 'usage'
-ALL = [SENSOR_SMS, SENSOR_USAGE]
+SENSOR_UNITS = {
+ SENSOR_SMS: 'unread',
+ SENSOR_SMS_TOTAL: 'messages',
+ SENSOR_USAGE: 'MiB',
+ 'radio_quality': '%',
+ 'rx_level': 'dBm',
+ 'tx_level': 'dBm',
+ 'upstream': None,
+ 'connection_text': None,
+ 'connection_type': None,
+ 'current_ps_service_type': None,
+ 'register_network_display': None,
+ 'current_band': None,
+ 'cell_id': None,
+}
-DEFAULT = [SENSOR_USAGE]
+BINARY_SENSOR_MOBILE_CONNECTED = 'mobile_connected'
+
+BINARY_SENSOR_CLASSES = {
+ 'roaming': None,
+ 'wire_connected': DEVICE_CLASS_CONNECTIVITY,
+ BINARY_SENSOR_MOBILE_CONNECTED: DEVICE_CLASS_CONNECTIVITY,
+}
+
+ALL_SENSORS = list(SENSOR_UNITS)
+DEFAULT_SENSORS = [SENSOR_USAGE]
+
+ALL_BINARY_SENSORS = list(BINARY_SENSOR_CLASSES)
+DEFAULT_BINARY_SENSORS = [BINARY_SENSOR_MOBILE_CONNECTED]
diff --git a/homeassistant/components/netio/manifest.json b/homeassistant/components/netio/manifest.json
new file mode 100644
index 00000000000..e3675db04d7
--- /dev/null
+++ b/homeassistant/components/netio/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "netio",
+ "name": "Netio",
+ "documentation": "https://www.home-assistant.io/components/netio",
+ "requirements": [
+ "pynetio==0.1.9.1"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/netio/switch.py b/homeassistant/components/netio/switch.py
index 4492697406d..ddaa9ffe0ff 100644
--- a/homeassistant/components/netio/switch.py
+++ b/homeassistant/components/netio/switch.py
@@ -1,9 +1,4 @@
-"""
-The Netio switch component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.netio/
-"""
+"""The Netio switch component."""
import logging
from collections import namedtuple
from datetime import timedelta
@@ -19,8 +14,6 @@ from homeassistant.const import (
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pynetio==0.1.9.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_START_DATE = 'start_date'
@@ -30,7 +23,6 @@ CONF_OUTLETS = 'outlets'
DEFAULT_PORT = 1234
DEFAULT_USERNAME = 'admin'
-DEPENDENCIES = ['http']
Device = namedtuple('device', ['netio', 'entities'])
DEVICES = {}
diff --git a/homeassistant/components/neurio_energy/manifest.json b/homeassistant/components/neurio_energy/manifest.json
new file mode 100644
index 00000000000..04420d5c4f2
--- /dev/null
+++ b/homeassistant/components/neurio_energy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "neurio_energy",
+ "name": "Neurio energy",
+ "documentation": "https://www.home-assistant.io/components/neurio_energy",
+ "requirements": [
+ "neurio==0.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/neurio_energy/sensor.py b/homeassistant/components/neurio_energy/sensor.py
index 673cd8da724..5992ca70593 100644
--- a/homeassistant/components/neurio_energy/sensor.py
+++ b/homeassistant/components/neurio_energy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring a Neurio energy sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.neurio_energy/
-"""
+"""Support for monitoring a Neurio energy sensor."""
import logging
from datetime import timedelta
@@ -18,8 +13,6 @@ from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['neurio==0.3.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_API_SECRET = 'api_secret'
diff --git a/homeassistant/components/nfandroidtv/manifest.json b/homeassistant/components/nfandroidtv/manifest.json
new file mode 100644
index 00000000000..8f3d88b58ee
--- /dev/null
+++ b/homeassistant/components/nfandroidtv/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "nfandroidtv",
+ "name": "Nfandroidtv",
+ "documentation": "https://www.home-assistant.io/components/nfandroidtv",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nfandroidtv/notify.py b/homeassistant/components/nfandroidtv/notify.py
index c4003a6312a..1cf1fbd0dbc 100644
--- a/homeassistant/components/nfandroidtv/notify.py
+++ b/homeassistant/components/nfandroidtv/notify.py
@@ -1,9 +1,4 @@
-"""
-Notifications for Android TV notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.nfandroidtv/
-"""
+"""Notifications for Android TV notification service."""
import base64
import io
import logging
diff --git a/homeassistant/components/niko_home_control/light.py b/homeassistant/components/niko_home_control/light.py
index 6b58ced5989..17b5f60cf44 100644
--- a/homeassistant/components/niko_home_control/light.py
+++ b/homeassistant/components/niko_home_control/light.py
@@ -1,61 +1,66 @@
-"""
-Support for Niko Home Control.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/light.niko_home_control/
-"""
+"""Support for Niko Home Control."""
+from datetime import timedelta
import logging
import voluptuous as vol
+# Import the device class from the component that you want to support
from homeassistant.components.light import (
ATTR_BRIGHTNESS, PLATFORM_SCHEMA, Light)
from homeassistant.const import CONF_HOST
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['niko-home-control==0.1.8']
+from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
+MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1)
+SCAN_INTERVAL = timedelta(seconds=30)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
})
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Niko Home Control light platform."""
import nikohomecontrol
-
host = config[CONF_HOST]
try:
- hub = nikohomecontrol.Hub({
+ nhc = nikohomecontrol.NikoHomeControl({
'ip': host,
'port': 8000,
- 'timeout': 20000,
- 'events': True
+ 'timeout': 20000
})
+ niko_data = NikoHomeControlData(hass, nhc)
+ await niko_data.async_update()
except OSError as err:
_LOGGER.error("Unable to access %s (%s)", host, err)
raise PlatformNotReady
- add_entities(
- [NikoHomeControlLight(light, hub) for light in hub.list_actions()],
- True)
+ async_add_entities([
+ NikoHomeControlLight(light, niko_data) for light in nhc.list_actions()
+ ], True)
class NikoHomeControlLight(Light):
"""Representation of an Niko Light."""
- def __init__(self, light, nhc):
+ def __init__(self, light, data):
"""Set up the Niko Home Control light platform."""
- self._nhc = nhc
+ self._data = data
self._light = light
+ self._unique_id = "light-{}".format(light.id)
self._name = light.name
- self._state = None
+ self._state = light.is_on
self._brightness = None
+ @property
+ def unique_id(self):
+ """Return unique ID for light."""
+ return self._unique_id
+
@property
def name(self):
"""Return the display name of this light."""
@@ -74,13 +79,46 @@ class NikoHomeControlLight(Light):
def turn_on(self, **kwargs):
"""Instruct the light to turn on."""
self._light.brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
+ _LOGGER.debug('Turn on: %s', self.name)
self._light.turn_on()
def turn_off(self, **kwargs):
"""Instruct the light to turn off."""
+ _LOGGER.debug('Turn off: %s', self.name)
self._light.turn_off()
- def update(self):
- """Fetch new state data for this light."""
- self._light.update()
- self._state = self._light.is_on
+ async def async_update(self):
+ """Get the latest data from NikoHomeControl API."""
+ await self._data.async_update()
+ self._state = self._data.get_state(self._light.id)
+
+
+class NikoHomeControlData:
+ """The class for handling data retrieval."""
+
+ def __init__(self, hass, nhc):
+ """Set up Niko Home Control Data object."""
+ self._nhc = nhc
+ self.hass = hass
+ self.available = True
+ self.data = {}
+ self._system_info = None
+
+ @Throttle(MIN_TIME_BETWEEN_UPDATES)
+ async def async_update(self):
+ """Get the latest data from the NikoHomeControl API."""
+ _LOGGER.debug('Fetching async state in bulk')
+ try:
+ self.data = await self.hass.async_add_executor_job(
+ self._nhc.list_actions_raw)
+ self.available = True
+ except OSError as ex:
+ _LOGGER.error("Unable to retrieve data from Niko, %s", str(ex))
+ self.available = False
+
+ def get_state(self, aid):
+ """Find and filter state based on action id."""
+ for state in self.data:
+ if state['id'] == aid:
+ return state['value1'] != 0
+ _LOGGER.error("Failed to retrieve state off unknown light")
diff --git a/homeassistant/components/niko_home_control/manifest.json b/homeassistant/components/niko_home_control/manifest.json
new file mode 100644
index 00000000000..8cb58a7b74c
--- /dev/null
+++ b/homeassistant/components/niko_home_control/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "niko_home_control",
+ "name": "Niko home control",
+ "documentation": "https://www.home-assistant.io/components/niko_home_control",
+ "requirements": [
+ "niko-home-control==0.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nilu/air_quality.py b/homeassistant/components/nilu/air_quality.py
index 2ab38c1ad95..cdc09976521 100644
--- a/homeassistant/components/nilu/air_quality.py
+++ b/homeassistant/components/nilu/air_quality.py
@@ -1,9 +1,4 @@
-"""
-Sensor for checking the air quality around Norway.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/air_quality.nilu/
-"""
+"""Sensor for checking the air quality around Norway."""
from datetime import timedelta
import logging
@@ -16,8 +11,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['niluclient==0.1.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_AREA = 'area'
diff --git a/homeassistant/components/nilu/manifest.json b/homeassistant/components/nilu/manifest.json
new file mode 100644
index 00000000000..ee7645653e6
--- /dev/null
+++ b/homeassistant/components/nilu/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nilu",
+ "name": "Nilu",
+ "documentation": "https://www.home-assistant.io/components/nilu",
+ "requirements": [
+ "niluclient==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py
index cb101c0a530..f9e7cd7f2d1 100644
--- a/homeassistant/components/nissan_leaf/__init__.py
+++ b/homeassistant/components/nissan_leaf/__init__.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['pycarwings2==2.8']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'nissan_leaf'
diff --git a/homeassistant/components/nissan_leaf/binary_sensor.py b/homeassistant/components/nissan_leaf/binary_sensor.py
index 5c71cf1fc51..5456fdc913a 100644
--- a/homeassistant/components/nissan_leaf/binary_sensor.py
+++ b/homeassistant/components/nissan_leaf/binary_sensor.py
@@ -7,8 +7,6 @@ from . import DATA_CHARGING, DATA_LEAF, DATA_PLUGGED_IN, LeafEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['nissan_leaf']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up of a Nissan Leaf binary sensor."""
diff --git a/homeassistant/components/nissan_leaf/device_tracker.py b/homeassistant/components/nissan_leaf/device_tracker.py
index 95f6fcdcaf1..0e2dca25ca6 100644
--- a/homeassistant/components/nissan_leaf/device_tracker.py
+++ b/homeassistant/components/nissan_leaf/device_tracker.py
@@ -8,8 +8,6 @@ from . import DATA_LEAF, DATA_LOCATION, SIGNAL_UPDATE_LEAF
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['nissan_leaf']
-
ICON_CAR = "mdi:car"
diff --git a/homeassistant/components/nissan_leaf/manifest.json b/homeassistant/components/nissan_leaf/manifest.json
new file mode 100644
index 00000000000..ab94c01b7c1
--- /dev/null
+++ b/homeassistant/components/nissan_leaf/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "nissan_leaf",
+ "name": "Nissan leaf",
+ "documentation": "https://www.home-assistant.io/components/nissan_leaf",
+ "requirements": [
+ "pycarwings2==2.8"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@filcole"
+ ]
+}
diff --git a/homeassistant/components/nissan_leaf/sensor.py b/homeassistant/components/nissan_leaf/sensor.py
index 682f482b488..064a96a64a1 100644
--- a/homeassistant/components/nissan_leaf/sensor.py
+++ b/homeassistant/components/nissan_leaf/sensor.py
@@ -12,8 +12,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['nissan_leaf']
-
ICON_RANGE = 'mdi:speedometer'
diff --git a/homeassistant/components/nissan_leaf/switch.py b/homeassistant/components/nissan_leaf/switch.py
index e6d72103a6c..27f81b69dd7 100644
--- a/homeassistant/components/nissan_leaf/switch.py
+++ b/homeassistant/components/nissan_leaf/switch.py
@@ -7,8 +7,6 @@ from . import DATA_CLIMATE, DATA_LEAF, LeafEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['nissan_leaf']
-
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Nissan Leaf switch platform setup."""
diff --git a/homeassistant/components/nmap_tracker/device_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py
index e553d323b72..3537f01b2b8 100644
--- a/homeassistant/components/nmap_tracker/device_tracker.py
+++ b/homeassistant/components/nmap_tracker/device_tracker.py
@@ -13,8 +13,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOSTS
-REQUIREMENTS = ['python-nmap==0.6.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE = 'exclude'
diff --git a/homeassistant/components/nmap_tracker/manifest.json b/homeassistant/components/nmap_tracker/manifest.json
new file mode 100644
index 00000000000..f4c4d33f036
--- /dev/null
+++ b/homeassistant/components/nmap_tracker/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nmap_tracker",
+ "name": "Nmap tracker",
+ "documentation": "https://www.home-assistant.io/components/nmap_tracker",
+ "requirements": [
+ "python-nmap==0.6.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nmbs/manifest.json b/homeassistant/components/nmbs/manifest.json
new file mode 100644
index 00000000000..1a2fa055688
--- /dev/null
+++ b/homeassistant/components/nmbs/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "nmbs",
+ "name": "Nmbs",
+ "documentation": "https://www.home-assistant.io/components/nmbs",
+ "requirements": [
+ "pyrail==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@thibmaek"
+ ]
+}
diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py
index 15f29339087..799225968e5 100644
--- a/homeassistant/components/nmbs/sensor.py
+++ b/homeassistant/components/nmbs/sensor.py
@@ -1,9 +1,4 @@
-"""
-Get ride details and liveboard details for NMBS (Belgian railway).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.nmbs/
-"""
+"""Get ride details and liveboard details for NMBS (Belgian railway)."""
import logging
import voluptuous as vol
@@ -28,8 +23,6 @@ CONF_STATION_TO = 'station_to'
CONF_STATION_LIVE = 'station_live'
CONF_EXCLUDE_VIAS = 'exclude_vias'
-REQUIREMENTS = ["pyrail==0.0.3"]
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STATION_FROM): cv.string,
vol.Required(CONF_STATION_TO): cv.string,
diff --git a/homeassistant/components/no_ip/manifest.json b/homeassistant/components/no_ip/manifest.json
new file mode 100644
index 00000000000..12581599532
--- /dev/null
+++ b/homeassistant/components/no_ip/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "no_ip",
+ "name": "No ip",
+ "documentation": "https://www.home-assistant.io/components/no_ip",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/noaa_tides/manifest.json b/homeassistant/components/noaa_tides/manifest.json
new file mode 100644
index 00000000000..9ffc0215fd1
--- /dev/null
+++ b/homeassistant/components/noaa_tides/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "noaa_tides",
+ "name": "Noaa tides",
+ "documentation": "https://www.home-assistant.io/components/noaa_tides",
+ "requirements": [
+ "py_noaa==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py
index 6a72fdf8f2a..0749f13031f 100644
--- a/homeassistant/components/noaa_tides/sensor.py
+++ b/homeassistant/components/noaa_tides/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the NOAA Tides and Currents API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.noaa_tides/
-"""
+"""Support for the NOAA Tides and Currents API."""
from datetime import datetime, timedelta
import logging
@@ -15,8 +10,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['py_noaa==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_STATION_ID = 'station_id'
diff --git a/homeassistant/components/norway_air/air_quality.py b/homeassistant/components/norway_air/air_quality.py
index 712f2734ea8..f2d5d87be47 100644
--- a/homeassistant/components/norway_air/air_quality.py
+++ b/homeassistant/components/norway_air/air_quality.py
@@ -1,9 +1,4 @@
-"""
-Sensor for checking the air quality forecast around Norway.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/air_quality.norway_air/
-"""
+"""Sensor for checking the air quality forecast around Norway."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.const import (CONF_LATITUDE, CONF_LONGITUDE,
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['pyMetno==0.4.6']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Air quality from " \
diff --git a/homeassistant/components/norway_air/manifest.json b/homeassistant/components/norway_air/manifest.json
new file mode 100644
index 00000000000..08c9932c36f
--- /dev/null
+++ b/homeassistant/components/norway_air/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "norway_air",
+ "name": "Norway air",
+ "documentation": "https://www.home-assistant.io/components/norway_air",
+ "requirements": [
+ "pyMetno==0.4.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py
index f0320617e19..8bb3384aebd 100644
--- a/homeassistant/components/notify/__init__.py
+++ b/homeassistant/components/notify/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provides functionality to notify people.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/notify/
-"""
+"""Provides functionality to notify people."""
import asyncio
import logging
from functools import partial
diff --git a/homeassistant/components/notify/manifest.json b/homeassistant/components/notify/manifest.json
new file mode 100644
index 00000000000..22c85723cb8
--- /dev/null
+++ b/homeassistant/components/notify/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "notify",
+ "name": "Notify",
+ "documentation": "https://www.home-assistant.io/components/notify",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@flowolf"
+ ]
+}
diff --git a/homeassistant/components/nsw_fuel_station/manifest.json b/homeassistant/components/nsw_fuel_station/manifest.json
new file mode 100644
index 00000000000..6be24fb5a2c
--- /dev/null
+++ b/homeassistant/components/nsw_fuel_station/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "nsw_fuel_station",
+ "name": "Nsw fuel station",
+ "documentation": "https://www.home-assistant.io/components/nsw_fuel_station",
+ "requirements": [
+ "nsw-fuel-api-client==1.0.10"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@nickw444"
+ ]
+}
diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py
index f0da619a6e7..9bb24973f45 100644
--- a/homeassistant/components/nsw_fuel_station/sensor.py
+++ b/homeassistant/components/nsw_fuel_station/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor platform to display the current fuel prices at a NSW fuel station.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.nsw_fuel_station/
-"""
+"""Sensor platform to display the current fuel prices at a NSW fuel station."""
import datetime
import logging
from typing import Optional
@@ -16,8 +11,6 @@ from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['nsw-fuel-api-client==1.0.10']
-
_LOGGER = logging.getLogger(__name__)
ATTR_STATION_ID = 'station_id'
diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py
index 38491feb32f..7a6d681bfbb 100644
--- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py
+++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['geojson_client==0.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CATEGORY = 'category'
diff --git a/homeassistant/components/nsw_rural_fire_service_feed/manifest.json b/homeassistant/components/nsw_rural_fire_service_feed/manifest.json
new file mode 100644
index 00000000000..dd0ba048a34
--- /dev/null
+++ b/homeassistant/components/nsw_rural_fire_service_feed/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nsw_rural_fire_service_feed",
+ "name": "Nsw rural fire service feed",
+ "documentation": "https://www.home-assistant.io/components/nsw_rural_fire_service_feed",
+ "requirements": [
+ "geojson_client==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nuheat/__init__.py b/homeassistant/components/nuheat/__init__.py
index 4ea37339ef3..f8227391ffd 100644
--- a/homeassistant/components/nuheat/__init__.py
+++ b/homeassistant/components/nuheat/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_DEVICES
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
-REQUIREMENTS = ["nuheat==0.3.0"]
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'nuheat'
diff --git a/homeassistant/components/nuheat/climate.py b/homeassistant/components/nuheat/climate.py
index 27e909f8f04..6a391679b89 100644
--- a/homeassistant/components/nuheat/climate.py
+++ b/homeassistant/components/nuheat/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for NuHeat thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.nuheat/
-"""
+"""Support for NuHeat thermostats."""
from datetime import timedelta
import logging
@@ -20,8 +15,6 @@ from homeassistant.util import Throttle
from . import DOMAIN as NUHEAT_DOMAIN
-DEPENDENCIES = ["nuheat"]
-
_LOGGER = logging.getLogger(__name__)
ICON = "mdi:thermometer"
diff --git a/homeassistant/components/nuheat/manifest.json b/homeassistant/components/nuheat/manifest.json
new file mode 100644
index 00000000000..c9e69c44ec2
--- /dev/null
+++ b/homeassistant/components/nuheat/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nuheat",
+ "name": "Nuheat",
+ "documentation": "https://www.home-assistant.io/components/nuheat",
+ "requirements": [
+ "nuheat==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nuheat/services.yaml b/homeassistant/components/nuheat/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/nuimo_controller/__init__.py b/homeassistant/components/nuimo_controller/__init__.py
index 70509469d2b..ca1de204a39 100644
--- a/homeassistant/components/nuimo_controller/__init__.py
+++ b/homeassistant/components/nuimo_controller/__init__.py
@@ -8,10 +8,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (CONF_MAC, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = [
- '--only-binary=all ' # avoid compilation of gattlib
- 'nuimo==0.1.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'nuimo_controller'
diff --git a/homeassistant/components/nuimo_controller/manifest.json b/homeassistant/components/nuimo_controller/manifest.json
new file mode 100644
index 00000000000..9f18d2849f8
--- /dev/null
+++ b/homeassistant/components/nuimo_controller/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nuimo_controller",
+ "name": "Nuimo controller",
+ "documentation": "https://www.home-assistant.io/components/nuimo_controller",
+ "requirements": [
+ "--only-binary=all nuimo==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nuimo_controller/services.yaml b/homeassistant/components/nuimo_controller/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py
index 8af798e31e0..0d045237858 100644
--- a/homeassistant/components/nuki/lock.py
+++ b/homeassistant/components/nuki/lock.py
@@ -1,9 +1,4 @@
-"""
-Nuki.io lock platform.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/lock.nuki/
-"""
+"""Nuki.io lock platform."""
from datetime import timedelta
import logging
@@ -15,8 +10,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.service import extract_entity_ids
-REQUIREMENTS = ['pynuki==1.3.2']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_PORT = 8080
diff --git a/homeassistant/components/nuki/manifest.json b/homeassistant/components/nuki/manifest.json
new file mode 100644
index 00000000000..d031cf6ce5f
--- /dev/null
+++ b/homeassistant/components/nuki/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "nuki",
+ "name": "Nuki",
+ "documentation": "https://www.home-assistant.io/components/nuki",
+ "requirements": [
+ "pynuki==1.3.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@pschmitt"
+ ]
+}
diff --git a/homeassistant/components/nuki/services.yaml b/homeassistant/components/nuki/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json
new file mode 100644
index 00000000000..920e56fba7c
--- /dev/null
+++ b/homeassistant/components/nut/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nut",
+ "name": "Nut",
+ "documentation": "https://www.home-assistant.io/components/nut",
+ "requirements": [
+ "pynut2==2.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py
index 1464c0d91c1..1a4ce779878 100644
--- a/homeassistant/components/nut/sensor.py
+++ b/homeassistant/components/nut/sensor.py
@@ -1,9 +1,4 @@
-"""
-Provides a sensor to track various status aspects of a UPS.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.nut/
-"""
+"""Provides a sensor to track various status aspects of a UPS."""
import logging
from datetime import timedelta
@@ -19,8 +14,6 @@ from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pynut2==2.1.2']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'NUT UPS'
diff --git a/homeassistant/components/nx584/alarm_control_panel.py b/homeassistant/components/nx584/alarm_control_panel.py
index c84872d0b25..4c6c604c950 100644
--- a/homeassistant/components/nx584/alarm_control_panel.py
+++ b/homeassistant/components/nx584/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for NX584 alarm control panels.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.nx584/
-"""
+"""Support for NX584 alarm control panels."""
import logging
import requests
@@ -16,8 +11,6 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pynx584==0.4']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = 'localhost'
diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py
index 2929acc2709..2162d742022 100644
--- a/homeassistant/components/nx584/binary_sensor.py
+++ b/homeassistant/components/nx584/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for exposing NX584 elements as sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.nx584/
-"""
+"""Support for exposing NX584 elements as sensors."""
import logging
import threading
import time
@@ -16,8 +11,6 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pynx584==0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_ZONES = 'exclude_zones'
diff --git a/homeassistant/components/nx584/manifest.json b/homeassistant/components/nx584/manifest.json
new file mode 100644
index 00000000000..67b5b0e2eeb
--- /dev/null
+++ b/homeassistant/components/nx584/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "nx584",
+ "name": "Nx584",
+ "documentation": "https://www.home-assistant.io/components/nx584",
+ "requirements": [
+ "pynx584==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nzbget/manifest.json b/homeassistant/components/nzbget/manifest.json
new file mode 100644
index 00000000000..69293ede516
--- /dev/null
+++ b/homeassistant/components/nzbget/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "nzbget",
+ "name": "Nzbget",
+ "documentation": "https://www.home-assistant.io/components/nzbget",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py
index 2ee8e37a57a..bb0b7c912fd 100644
--- a/homeassistant/components/nzbget/sensor.py
+++ b/homeassistant/components/nzbget/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring NZBGet NZB client.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.nzbget/
-"""
+"""Support for monitoring NZBGet NZB client."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/oasa_telematics/__init__.py b/homeassistant/components/oasa_telematics/__init__.py
new file mode 100644
index 00000000000..3629f31982b
--- /dev/null
+++ b/homeassistant/components/oasa_telematics/__init__.py
@@ -0,0 +1 @@
+"""The OASA Telematics component."""
diff --git a/homeassistant/components/oasa_telematics/manifest.json b/homeassistant/components/oasa_telematics/manifest.json
new file mode 100644
index 00000000000..15bf40e63c8
--- /dev/null
+++ b/homeassistant/components/oasa_telematics/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "oasa_telematics",
+ "name": "OASA Telematics",
+ "documentation": "https://www.home-assistant.io/components/oasa_telematics/",
+ "requirements": [
+ "oasatelematics==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
\ No newline at end of file
diff --git a/homeassistant/components/oasa_telematics/sensor.py b/homeassistant/components/oasa_telematics/sensor.py
new file mode 100644
index 00000000000..60c2f9a231b
--- /dev/null
+++ b/homeassistant/components/oasa_telematics/sensor.py
@@ -0,0 +1,191 @@
+"""Support for OASA Telematics from telematics.oasa.gr."""
+import logging
+from datetime import timedelta
+from operator import itemgetter
+
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+ CONF_NAME, ATTR_ATTRIBUTION, DEVICE_CLASS_TIMESTAMP)
+from homeassistant.helpers.entity import Entity
+from homeassistant.util import dt as dt_util
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_STOP_ID = 'stop_id'
+ATTR_STOP_NAME = 'stop_name'
+ATTR_ROUTE_ID = 'route_id'
+ATTR_ROUTE_NAME = 'route_name'
+ATTR_NEXT_ARRIVAL = 'next_arrival'
+ATTR_SECOND_NEXT_ARRIVAL = 'second_next_arrival'
+ATTR_NEXT_DEPARTURE = 'next_departure'
+
+ATTRIBUTION = "Data retrieved from telematics.oasa.gr"
+
+CONF_STOP_ID = 'stop_id'
+CONF_ROUTE_ID = 'route_id'
+
+DEFAULT_NAME = 'OASA Telematics'
+ICON = 'mdi:bus'
+
+SCAN_INTERVAL = timedelta(seconds=60)
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_STOP_ID): cv.string,
+ vol.Required(CONF_ROUTE_ID): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
+})
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the OASA Telematics sensor."""
+ name = config[CONF_NAME]
+ stop_id = config[CONF_STOP_ID]
+ route_id = config.get(CONF_ROUTE_ID)
+
+ data = OASATelematicsData(stop_id, route_id)
+
+ add_entities([OASATelematicsSensor(
+ data, stop_id, route_id, name)], True)
+
+
+class OASATelematicsSensor(Entity):
+ """Implementation of the OASA Telematics sensor."""
+
+ def __init__(self, data, stop_id, route_id, name):
+ """Initialize the sensor."""
+ self.data = data
+ self._name = name
+ self._stop_id = stop_id
+ self._route_id = route_id
+ self._name_data = self._times = self._state = None
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @property
+ def device_class(self):
+ """Return the class of this sensor."""
+ return DEVICE_CLASS_TIMESTAMP
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return self._state
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ params = {}
+ if self._times is not None:
+ next_arrival_data = self._times[0]
+ if ATTR_NEXT_ARRIVAL in next_arrival_data:
+ next_arrival = next_arrival_data[ATTR_NEXT_ARRIVAL]
+ params.update({
+ ATTR_NEXT_ARRIVAL: next_arrival.isoformat()
+ })
+ if len(self._times) > 1:
+ second_next_arrival_time = self._times[1][ATTR_NEXT_ARRIVAL]
+ if second_next_arrival_time is not None:
+ second_arrival = second_next_arrival_time
+ params.update({
+ ATTR_SECOND_NEXT_ARRIVAL: second_arrival.isoformat()
+ })
+ params.update({
+ ATTR_ROUTE_ID: self._times[0][ATTR_ROUTE_ID],
+ ATTR_STOP_ID: self._stop_id,
+ ATTR_ATTRIBUTION: ATTRIBUTION,
+ })
+ params.update({
+ ATTR_ROUTE_NAME: self._name_data[ATTR_ROUTE_NAME],
+ ATTR_STOP_NAME: self._name_data[ATTR_STOP_NAME]
+ })
+ return {k: v for k, v in params.items() if v}
+
+ @property
+ def icon(self):
+ """Icon to use in the frontend, if any."""
+ return ICON
+
+ def update(self):
+ """Get the latest data from OASA API and update the states."""
+ self.data.update()
+ self._times = self.data.info
+ self._name_data = self.data.name_data
+ next_arrival_data = self._times[0]
+ if ATTR_NEXT_ARRIVAL in next_arrival_data:
+ self._state = next_arrival_data[ATTR_NEXT_ARRIVAL].isoformat()
+
+
+class OASATelematicsData():
+ """The class for handling data retrieval."""
+
+ def __init__(self, stop_id, route_id):
+ """Initialize the data object."""
+ import oasatelematics
+ self.stop_id = stop_id
+ self.route_id = route_id
+ self.info = self.empty_result()
+ self.oasa_api = oasatelematics
+ self.name_data = {ATTR_ROUTE_NAME: self.get_route_name(),
+ ATTR_STOP_NAME: self.get_stop_name()}
+
+ def empty_result(self):
+ """Object returned when no arrivals are found."""
+ return [{ATTR_ROUTE_ID: self.route_id}]
+
+ def get_route_name(self):
+ """Get the route name from the API."""
+ try:
+ route = self.oasa_api.getRouteName(self.route_id)
+ if route:
+ return route[0].get('route_departure_eng')
+ except TypeError:
+ _LOGGER.error("Cannot get route name from OASA API")
+ return None
+
+ def get_stop_name(self):
+ """Get the stop name from the API."""
+ try:
+ name_data = self.oasa_api.getStopNameAndXY(self.stop_id)
+ if name_data:
+ return name_data[0].get('stop_descr_matrix_eng')
+ except TypeError:
+ _LOGGER.error("Cannot get stop name from OASA API")
+ return None
+
+ def update(self):
+ """Get the latest arrival data from telematics.oasa.gr API."""
+ self.info = []
+
+ results = self.oasa_api.getStopArrivals(self.stop_id)
+
+ if not results:
+ self.info = self.empty_result()
+ return
+
+ # Parse results
+ results = [r for r in results if r.get('route_code') in self.route_id]
+ current_time = dt_util.utcnow()
+
+ for result in results:
+ btime2 = result.get('btime2')
+ if btime2 is not None:
+ arrival_min = int(btime2)
+ timestamp = current_time + timedelta(minutes=arrival_min)
+ arrival_data = {ATTR_NEXT_ARRIVAL: timestamp,
+ ATTR_ROUTE_ID: self.route_id}
+ self.info.append(arrival_data)
+
+ if not self.info:
+ _LOGGER.debug("No arrivals with given parameters")
+ self.info = self.empty_result()
+ return
+
+ # Sort the data by time
+ sort = sorted(self.info, itemgetter(ATTR_NEXT_ARRIVAL))
+ self.info = sort
diff --git a/homeassistant/components/octoprint/binary_sensor.py b/homeassistant/components/octoprint/binary_sensor.py
index be3381f3bc8..d505c88071e 100644
--- a/homeassistant/components/octoprint/binary_sensor.py
+++ b/homeassistant/components/octoprint/binary_sensor.py
@@ -9,8 +9,6 @@ from . import BINARY_SENSOR_TYPES, DOMAIN as COMPONENT_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['octoprint']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available OctoPrint binary sensors."""
diff --git a/homeassistant/components/octoprint/manifest.json b/homeassistant/components/octoprint/manifest.json
new file mode 100644
index 00000000000..c34e1458e4b
--- /dev/null
+++ b/homeassistant/components/octoprint/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "octoprint",
+ "name": "Octoprint",
+ "documentation": "https://www.home-assistant.io/components/octoprint",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py
index f07d88d11da..979f56290c1 100644
--- a/homeassistant/components/octoprint/sensor.py
+++ b/homeassistant/components/octoprint/sensor.py
@@ -10,8 +10,6 @@ from . import DOMAIN as COMPONENT_DOMAIN, SENSOR_TYPES
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['octoprint']
-
NOTIFICATION_ID = 'octoprint_notification'
NOTIFICATION_TITLE = 'OctoPrint sensor setup error'
diff --git a/homeassistant/components/oem/climate.py b/homeassistant/components/oem/climate.py
index f1e03396b05..3ae9b4dad5c 100644
--- a/homeassistant/components/oem/climate.py
+++ b/homeassistant/components/oem/climate.py
@@ -21,8 +21,6 @@ from homeassistant.const import (
CONF_PORT, TEMP_CELSIUS, CONF_NAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['oemthermostat==1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = 'away_temp'
diff --git a/homeassistant/components/oem/manifest.json b/homeassistant/components/oem/manifest.json
new file mode 100644
index 00000000000..d23b07b2756
--- /dev/null
+++ b/homeassistant/components/oem/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "oem",
+ "name": "Oem",
+ "documentation": "https://www.home-assistant.io/components/oem",
+ "requirements": [
+ "oemthermostat==1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ohmconnect/manifest.json b/homeassistant/components/ohmconnect/manifest.json
new file mode 100644
index 00000000000..a163a7d673a
--- /dev/null
+++ b/homeassistant/components/ohmconnect/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ohmconnect",
+ "name": "Ohmconnect",
+ "documentation": "https://www.home-assistant.io/components/ohmconnect",
+ "requirements": [
+ "defusedxml==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py
index 3487cab2fcd..87dca2aa853 100644
--- a/homeassistant/components/ohmconnect/sensor.py
+++ b/homeassistant/components/ohmconnect/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for OhmConnect.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/sensor.ohmconnect/
-"""
+"""Support for OhmConnect."""
import logging
from datetime import timedelta
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['defusedxml==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_ID = 'id'
diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py
index f8885962ee7..29371369c70 100644
--- a/homeassistant/components/onboarding/__init__.py
+++ b/homeassistant/components/onboarding/__init__.py
@@ -4,8 +4,6 @@ from homeassistant.loader import bind_hass
from .const import DOMAIN, STEP_USER, STEPS
-DEPENDENCIES = ['auth', 'http']
-
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
diff --git a/homeassistant/components/onboarding/manifest.json b/homeassistant/components/onboarding/manifest.json
new file mode 100644
index 00000000000..ffb01bd5602
--- /dev/null
+++ b/homeassistant/components/onboarding/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "onboarding",
+ "name": "Onboarding",
+ "documentation": "https://www.home-assistant.io/components/onboarding",
+ "requirements": [],
+ "dependencies": [
+ "auth",
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json
new file mode 100644
index 00000000000..00075d4485f
--- /dev/null
+++ b/homeassistant/components/onewire/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "onewire",
+ "name": "Onewire",
+ "documentation": "https://www.home-assistant.io/components/onewire",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py
index d39ab24230c..2e55b5cea36 100644
--- a/homeassistant/components/onewire/sensor.py
+++ b/homeassistant/components/onewire/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for 1-Wire environment sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.onewire/
-"""
+"""Support for 1-Wire environment sensors."""
import os
import time
import logging
diff --git a/homeassistant/components/onkyo/manifest.json b/homeassistant/components/onkyo/manifest.json
new file mode 100644
index 00000000000..7fd27dd7edf
--- /dev/null
+++ b/homeassistant/components/onkyo/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "onkyo",
+ "name": "Onkyo",
+ "documentation": "https://www.home-assistant.io/components/onkyo",
+ "requirements": [
+ "onkyo-eiscp==1.2.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py
index 2fb284bb24a..0a8a459731e 100644
--- a/homeassistant/components/onkyo/media_player.py
+++ b/homeassistant/components/onkyo/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Onkyo Receivers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.onkyo/
-"""
+"""Support for Onkyo Receivers."""
import logging
# pylint: disable=unused-import
@@ -21,8 +16,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, ATTR_ENTITY_ID)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['onkyo-eiscp==1.2.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_SOURCES = 'sources'
diff --git a/homeassistant/components/onkyo/services.yaml b/homeassistant/components/onkyo/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py
index a196f87cd10..6a773a854c9 100644
--- a/homeassistant/components/onvif/camera.py
+++ b/homeassistant/components/onvif/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for ONVIF Cameras with FFmpeg as decoder.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.onvif/
-"""
+"""Support for ONVIF Cameras with FFmpeg as decoder."""
import asyncio
import logging
import os
@@ -25,10 +20,6 @@ from homeassistant.helpers.service import extract_entity_ids
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['onvif-py3==0.1.3',
- 'suds-py3==1.3.3.0',
- 'suds-passworddigest-homeassistant==0.1.2a0.dev0']
-DEPENDENCIES = ['ffmpeg']
DEFAULT_NAME = 'ONVIF Camera'
DEFAULT_PORT = 5000
DEFAULT_USERNAME = 'admin'
diff --git a/homeassistant/components/onvif/manifest.json b/homeassistant/components/onvif/manifest.json
new file mode 100644
index 00000000000..bade9f37022
--- /dev/null
+++ b/homeassistant/components/onvif/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "onvif",
+ "name": "Onvif",
+ "documentation": "https://www.home-assistant.io/components/onvif",
+ "requirements": [
+ "onvif-py3==0.1.3",
+ "suds-passworddigest-homeassistant==0.1.2a0.dev0",
+ "suds-py3==1.3.3.0"
+ ],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/onvif/services.yaml b/homeassistant/components/onvif/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/openalpr_cloud/image_processing.py b/homeassistant/components/openalpr_cloud/image_processing.py
index c983a945548..12146009fac 100644
--- a/homeassistant/components/openalpr_cloud/image_processing.py
+++ b/homeassistant/components/openalpr_cloud/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will help set the OpenALPR cloud for ALPR processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.openalpr_cloud/
-"""
+"""Component that will help set the OpenALPR cloud for ALPR processing."""
import asyncio
import logging
from base64 import b64encode
diff --git a/homeassistant/components/openalpr_cloud/manifest.json b/homeassistant/components/openalpr_cloud/manifest.json
new file mode 100644
index 00000000000..f0421295836
--- /dev/null
+++ b/homeassistant/components/openalpr_cloud/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "openalpr_cloud",
+ "name": "Openalpr cloud",
+ "documentation": "https://www.home-assistant.io/components/openalpr_cloud",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openalpr_local/image_processing.py b/homeassistant/components/openalpr_local/image_processing.py
index 4a98594d50c..811a160dd02 100644
--- a/homeassistant/components/openalpr_local/image_processing.py
+++ b/homeassistant/components/openalpr_local/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Component that will help set the OpenALPR local for ALPR processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.openalpr_local/
-"""
+"""Component that will help set the OpenALPR local for ALPR processing."""
import asyncio
import io
import logging
diff --git a/homeassistant/components/openalpr_local/manifest.json b/homeassistant/components/openalpr_local/manifest.json
new file mode 100644
index 00000000000..3c92e840f43
--- /dev/null
+++ b/homeassistant/components/openalpr_local/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "openalpr_local",
+ "name": "Openalpr local",
+ "documentation": "https://www.home-assistant.io/components/openalpr_local",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py
index 10173cdb725..4a28a37b705 100644
--- a/homeassistant/components/opencv/image_processing.py
+++ b/homeassistant/components/opencv/image_processing.py
@@ -11,8 +11,6 @@ from homeassistant.components.image_processing import (
from homeassistant.core import split_entity_id
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['numpy==1.16.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MATCHES = 'matches'
diff --git a/homeassistant/components/opencv/manifest.json b/homeassistant/components/opencv/manifest.json
new file mode 100644
index 00000000000..b49e5b73554
--- /dev/null
+++ b/homeassistant/components/opencv/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "opencv",
+ "name": "Opencv",
+ "documentation": "https://www.home-assistant.io/components/opencv",
+ "requirements": [
+ "numpy==1.16.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openevse/manifest.json b/homeassistant/components/openevse/manifest.json
new file mode 100644
index 00000000000..f37c769d20e
--- /dev/null
+++ b/homeassistant/components/openevse/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "openevse",
+ "name": "Openevse",
+ "documentation": "https://www.home-assistant.io/components/openevse",
+ "requirements": [
+ "openevsewifi==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openevse/sensor.py b/homeassistant/components/openevse/sensor.py
index cf41f87718d..efc4f8a0200 100644
--- a/homeassistant/components/openevse/sensor.py
+++ b/homeassistant/components/openevse/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring an OpenEVSE Charger.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.openevse/
-"""
+"""Support for monitoring an OpenEVSE Charger."""
import logging
from requests import RequestException
@@ -16,8 +11,6 @@ from homeassistant.const import (
CONF_MONITORED_VARIABLES)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['openevsewifi==0.4']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
diff --git a/homeassistant/components/openexchangerates/manifest.json b/homeassistant/components/openexchangerates/manifest.json
new file mode 100644
index 00000000000..ffb86d4a5e2
--- /dev/null
+++ b/homeassistant/components/openexchangerates/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "openexchangerates",
+ "name": "Openexchangerates",
+ "documentation": "https://www.home-assistant.io/components/openexchangerates",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py
index 6361b823dea..6c146044140 100644
--- a/homeassistant/components/openexchangerates/sensor.py
+++ b/homeassistant/components/openexchangerates/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for openexchangerates.org exchange rates service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.openexchangerates/
-"""
+"""Support for openexchangerates.org exchange rates service."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py
index 664d2e291ac..1e3d3128829 100644
--- a/homeassistant/components/opengarage/cover.py
+++ b/homeassistant/components/opengarage/cover.py
@@ -1,9 +1,4 @@
-"""
-Platform for the opengarage.io cover component.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/cover.opengarage/
-"""
+"""Platform for the opengarage.io cover component."""
import logging
import requests
diff --git a/homeassistant/components/opengarage/manifest.json b/homeassistant/components/opengarage/manifest.json
new file mode 100644
index 00000000000..95f944b7087
--- /dev/null
+++ b/homeassistant/components/opengarage/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "opengarage",
+ "name": "Opengarage",
+ "documentation": "https://www.home-assistant.io/components/opengarage",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openhardwaremonitor/manifest.json b/homeassistant/components/openhardwaremonitor/manifest.json
new file mode 100644
index 00000000000..d9281f08eda
--- /dev/null
+++ b/homeassistant/components/openhardwaremonitor/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "openhardwaremonitor",
+ "name": "Openhardwaremonitor",
+ "documentation": "https://www.home-assistant.io/components/openhardwaremonitor",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py
index d429cad9d97..7c5072db97c 100644
--- a/homeassistant/components/openhardwaremonitor/sensor.py
+++ b/homeassistant/components/openhardwaremonitor/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Open Hardware Monitor Sensor Platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.openhardwaremonitor/
-"""
+"""Support for Open Hardware Monitor Sensor Platform."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/openhome/manifest.json b/homeassistant/components/openhome/manifest.json
new file mode 100644
index 00000000000..276346ae79b
--- /dev/null
+++ b/homeassistant/components/openhome/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "openhome",
+ "name": "Openhome",
+ "documentation": "https://www.home-assistant.io/components/openhome",
+ "requirements": [
+ "openhomedevice==0.4.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py
index d828284a563..edb033b8f11 100644
--- a/homeassistant/components/openhome/media_player.py
+++ b/homeassistant/components/openhome/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Openhome Devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.openhome/
-"""
+"""Support for Openhome Devices."""
import logging
from homeassistant.components.media_player import (
@@ -15,8 +10,6 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import (
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
-REQUIREMENTS = ['openhomedevice==0.4.2']
-
SUPPORT_OPENHOME = SUPPORT_SELECT_SOURCE | \
SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
SUPPORT_TURN_OFF | SUPPORT_TURN_ON
diff --git a/homeassistant/components/opensensemap/air_quality.py b/homeassistant/components/opensensemap/air_quality.py
index 5407f65a1d8..3f859724fc3 100644
--- a/homeassistant/components/opensensemap/air_quality.py
+++ b/homeassistant/components/opensensemap/air_quality.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['opensensemap-api==0.1.5']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Data provided by openSenseMap'
diff --git a/homeassistant/components/opensensemap/manifest.json b/homeassistant/components/opensensemap/manifest.json
new file mode 100644
index 00000000000..ab03f1cf7c6
--- /dev/null
+++ b/homeassistant/components/opensensemap/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "opensensemap",
+ "name": "Opensensemap",
+ "documentation": "https://www.home-assistant.io/components/opensensemap",
+ "requirements": [
+ "opensensemap-api==0.1.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/opensky/manifest.json b/homeassistant/components/opensky/manifest.json
new file mode 100644
index 00000000000..dd58cdd4168
--- /dev/null
+++ b/homeassistant/components/opensky/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "opensky",
+ "name": "Opensky",
+ "documentation": "https://www.home-assistant.io/components/opensky",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py
index 5ee11af4e60..3019c54471f 100644
--- a/homeassistant/components/opensky/sensor.py
+++ b/homeassistant/components/opensky/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for the Open Sky Network.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.opensky/
-"""
+"""Sensor for the Open Sky Network."""
import logging
from datetime import timedelta
diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py
index 1476363c6bd..829344fb1f0 100644
--- a/homeassistant/components/opentherm_gw/__init__.py
+++ b/homeassistant/components/opentherm_gw/__init__.py
@@ -15,8 +15,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyotgw==0.4b3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'opentherm_gw'
diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py
index d0b60a25770..bf342cc9813 100644
--- a/homeassistant/components/opentherm_gw/binary_sensor.py
+++ b/homeassistant/components/opentherm_gw/binary_sensor.py
@@ -14,8 +14,6 @@ DEVICE_CLASS_COLD = 'cold'
DEVICE_CLASS_HEAT = 'heat'
DEVICE_CLASS_PROBLEM = 'problem'
-DEPENDENCIES = ['opentherm_gw']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py
index 60f1901d43e..2dbd7f3cf79 100644
--- a/homeassistant/components/opentherm_gw/climate.py
+++ b/homeassistant/components/opentherm_gw/climate.py
@@ -15,8 +15,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['opentherm_gw']
-
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json
new file mode 100644
index 00000000000..50bfa4d1122
--- /dev/null
+++ b/homeassistant/components/opentherm_gw/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "opentherm_gw",
+ "name": "Opentherm gw",
+ "documentation": "https://www.home-assistant.io/components/opentherm_gw",
+ "requirements": [
+ "pyotgw==0.4b3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py
index 5c64b8ab719..60ccedfd451 100644
--- a/homeassistant/components/opentherm_gw/sensor.py
+++ b/homeassistant/components/opentherm_gw/sensor.py
@@ -16,8 +16,6 @@ UNIT_KW = 'kW'
UNIT_L_MIN = 'L/min'
UNIT_PERCENT = '%'
-DEPENDENCIES = ['opentherm_gw']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/openuv/.translations/ko.json b/homeassistant/components/openuv/.translations/ko.json
index 5e06be81d31..c16481993ef 100644
--- a/homeassistant/components/openuv/.translations/ko.json
+++ b/homeassistant/components/openuv/.translations/ko.json
@@ -12,7 +12,7 @@
"latitude": "\uc704\ub3c4",
"longitude": "\uacbd\ub3c4"
},
- "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694"
+ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825"
}
},
"title": "OpenUV"
diff --git a/homeassistant/components/openuv/.translations/ru.json b/homeassistant/components/openuv/.translations/ru.json
index 38e261ab6bd..9683c5d7c36 100644
--- a/homeassistant/components/openuv/.translations/ru.json
+++ b/homeassistant/components/openuv/.translations/ru.json
@@ -7,7 +7,7 @@
"step": {
"user": {
"data": {
- "api_key": "\u041a\u043b\u044e\u0447 API OpenUV",
+ "api_key": "\u041a\u043b\u044e\u0447 API",
"elevation": "\u0412\u044b\u0441\u043e\u0442\u0430",
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430"
diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py
index 5533beb2fae..63d2744cd4d 100644
--- a/homeassistant/components/openuv/__init__.py
+++ b/homeassistant/components/openuv/__init__.py
@@ -11,12 +11,11 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.service import verify_domain_control
from .config_flow import configured_instances
from .const import DOMAIN
-REQUIREMENTS = ['pyopenuv==1.0.9']
-
_LOGGER = logging.getLogger(__name__)
DATA_OPENUV_CLIENT = 'data_client'
@@ -132,6 +131,8 @@ async def async_setup_entry(hass, config_entry):
from pyopenuv import Client
from pyopenuv.errors import OpenUvError
+ _verify_domain_control = verify_domain_control(hass, DOMAIN)
+
try:
websession = aiohttp_client.async_get_clientsession(hass)
openuv = OpenUV(
@@ -157,6 +158,7 @@ async def async_setup_entry(hass, config_entry):
hass.config_entries.async_forward_entry_setup(
config_entry, component))
+ @_verify_domain_control
async def update_data(service):
"""Refresh OpenUV data."""
_LOGGER.debug('Refreshing OpenUV data')
diff --git a/homeassistant/components/openuv/binary_sensor.py b/homeassistant/components/openuv/binary_sensor.py
index cfc82a75729..d02312f07f8 100644
--- a/homeassistant/components/openuv/binary_sensor.py
+++ b/homeassistant/components/openuv/binary_sensor.py
@@ -16,8 +16,6 @@ ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv'
ATTR_PROTECTION_WINDOW_STARTING_TIME = 'start_time'
ATTR_PROTECTION_WINDOW_STARTING_UV = 'start_uv'
-DEPENDENCIES = ['openuv']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/openuv/manifest.json b/homeassistant/components/openuv/manifest.json
new file mode 100644
index 00000000000..b94a409aa71
--- /dev/null
+++ b/homeassistant/components/openuv/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "openuv",
+ "name": "Openuv",
+ "documentation": "https://www.home-assistant.io/components/openuv",
+ "requirements": [
+ "pyopenuv==1.0.9"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py
index 42780d57b3c..2fa2e44c98e 100644
--- a/homeassistant/components/openuv/sensor.py
+++ b/homeassistant/components/openuv/sensor.py
@@ -14,8 +14,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['openuv']
-
ATTR_MAX_UV_TIME = 'time'
EXPOSURE_TYPE_MAP = {
diff --git a/homeassistant/components/openweathermap/manifest.json b/homeassistant/components/openweathermap/manifest.json
new file mode 100644
index 00000000000..d24b23f64bb
--- /dev/null
+++ b/homeassistant/components/openweathermap/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "openweathermap",
+ "name": "Openweathermap",
+ "documentation": "https://www.home-assistant.io/components/openweathermap",
+ "requirements": [
+ "pyowm==2.10.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/openweathermap/sensor.py b/homeassistant/components/openweathermap/sensor.py
index a137836138b..97ab9984d52 100644
--- a/homeassistant/components/openweathermap/sensor.py
+++ b/homeassistant/components/openweathermap/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the OpenWeatherMap (OWM) service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.openweathermap/
-"""
+"""Support for the OpenWeatherMap (OWM) service."""
from datetime import timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyowm==2.10.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by OpenWeatherMap"
diff --git a/homeassistant/components/openweathermap/weather.py b/homeassistant/components/openweathermap/weather.py
index 8a37bc97575..75755a53124 100644
--- a/homeassistant/components/openweathermap/weather.py
+++ b/homeassistant/components/openweathermap/weather.py
@@ -14,8 +14,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.util.pressure import convert as convert_pressure
-REQUIREMENTS = ['pyowm==2.10.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Data provided by OpenWeatherMap'
diff --git a/homeassistant/components/opple/light.py b/homeassistant/components/opple/light.py
index fb503d33d31..c3d66c52663 100644
--- a/homeassistant/components/opple/light.py
+++ b/homeassistant/components/opple/light.py
@@ -1,9 +1,4 @@
-"""
-Support for the Opple light.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.opple/
-"""
+"""Support for the Opple light."""
import logging
@@ -19,8 +14,6 @@ from homeassistant.util.color import \
from homeassistant.util.color import \
color_temperature_mired_to_kelvin as mired_to_kelvin
-REQUIREMENTS = ['pyoppleio==1.0.5']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "opple light"
diff --git a/homeassistant/components/opple/manifest.json b/homeassistant/components/opple/manifest.json
new file mode 100644
index 00000000000..c10be48f3fa
--- /dev/null
+++ b/homeassistant/components/opple/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "opple",
+ "name": "Opple",
+ "documentation": "https://www.home-assistant.io/components/opple",
+ "requirements": [
+ "pyoppleio==1.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/orvibo/manifest.json b/homeassistant/components/orvibo/manifest.json
new file mode 100644
index 00000000000..73f4eaed7da
--- /dev/null
+++ b/homeassistant/components/orvibo/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "orvibo",
+ "name": "Orvibo",
+ "documentation": "https://www.home-assistant.io/components/orvibo",
+ "requirements": [
+ "orvibo==1.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/orvibo/switch.py b/homeassistant/components/orvibo/switch.py
index 9e6686ca3ae..20b86dbf679 100644
--- a/homeassistant/components/orvibo/switch.py
+++ b/homeassistant/components/orvibo/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Orvibo S20 Wifi Smart Switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.orvibo/
-"""
+"""Support for Orvibo S20 Wifi Smart Switches."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_SWITCHES, CONF_MAC, CONF_DISCOVERY)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['orvibo==1.1.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Orvibo S20 Switch'
diff --git a/homeassistant/components/osramlightify/light.py b/homeassistant/components/osramlightify/light.py
index a49e12c76a6..dafab76a2dc 100644
--- a/homeassistant/components/osramlightify/light.py
+++ b/homeassistant/components/osramlightify/light.py
@@ -1,44 +1,35 @@
-"""
-Support for Osram Lightify.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.osramlightify/
-"""
-from datetime import timedelta
+"""Support for Osram Lightify."""
import logging
import random
import socket
import voluptuous as vol
-from homeassistant import util
from homeassistant.components.light import (
- ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR,
- ATTR_TRANSITION, EFFECT_RANDOM, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
+ ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION,
+ ATTR_EFFECT, EFFECT_RANDOM, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION,
Light)
+
from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
-from homeassistant.util.color import (
- color_temperature_kelvin_to_mired, color_temperature_mired_to_kelvin)
import homeassistant.util.color as color_util
-REQUIREMENTS = ['lightify==1.0.6.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ALLOW_LIGHTIFY_NODES = 'allow_lightify_nodes'
CONF_ALLOW_LIGHTIFY_GROUPS = 'allow_lightify_groups'
+CONF_ALLOW_LIGHTIFY_SENSORS = 'allow_lightify_sensors'
+CONF_ALLOW_LIGHTIFY_SWITCHES = 'allow_lightify_switches'
+CONF_INTERVAL_LIGHTIFY_STATUS = 'interval_lightify_status'
+CONF_INTERVAL_LIGHTIFY_CONF = 'interval_lightify_conf'
DEFAULT_ALLOW_LIGHTIFY_NODES = True
DEFAULT_ALLOW_LIGHTIFY_GROUPS = True
-
-MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
-MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
-
-SUPPORT_OSRAMLIGHTIFY = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP |
- SUPPORT_EFFECT | SUPPORT_COLOR |
- SUPPORT_TRANSITION)
+DEFAULT_ALLOW_LIGHTIFY_SENSORS = True
+DEFAULT_ALLOW_LIGHTIFY_SWITCHES = True
+DEFAULT_INTERVAL_LIGHTIFY_STATUS = 5
+DEFAULT_INTERVAL_LIGHTIFY_CONF = 3600
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
@@ -46,228 +37,376 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
default=DEFAULT_ALLOW_LIGHTIFY_NODES): cv.boolean,
vol.Optional(CONF_ALLOW_LIGHTIFY_GROUPS,
default=DEFAULT_ALLOW_LIGHTIFY_GROUPS): cv.boolean,
+ vol.Optional(CONF_ALLOW_LIGHTIFY_SENSORS,
+ default=DEFAULT_ALLOW_LIGHTIFY_SENSORS): cv.boolean,
+ vol.Optional(CONF_ALLOW_LIGHTIFY_SWITCHES,
+ default=DEFAULT_ALLOW_LIGHTIFY_SWITCHES): cv.boolean,
+ vol.Optional(CONF_INTERVAL_LIGHTIFY_STATUS,
+ default=DEFAULT_INTERVAL_LIGHTIFY_STATUS): cv.positive_int,
+ vol.Optional(CONF_INTERVAL_LIGHTIFY_CONF,
+ default=DEFAULT_INTERVAL_LIGHTIFY_CONF): cv.positive_int
})
+DEFAULT_BRIGHTNESS = 2
+DEFAULT_KELVIN = 2700
+
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Osram Lightify lights."""
import lightify
- host = config.get(CONF_HOST)
- add_nodes = config.get(CONF_ALLOW_LIGHTIFY_NODES)
- add_groups = config.get(CONF_ALLOW_LIGHTIFY_GROUPS)
-
+ host = config[CONF_HOST]
try:
- bridge = lightify.Lightify(host)
+ bridge = lightify.Lightify(host, log_level=logging.NOTSET)
except socket.error as err:
msg = "Error connecting to bridge: {} due to: {}".format(
host, str(err))
_LOGGER.exception(msg)
return
- setup_bridge(bridge, add_entities, add_nodes, add_groups)
+ setup_bridge(bridge, add_entities, config)
-def setup_bridge(bridge, add_entities, add_nodes, add_groups):
+def setup_bridge(bridge, add_entities, config):
"""Set up the Lightify bridge."""
lights = {}
+ groups = {}
+ groups_last_updated = [0]
- @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights():
- """Update the lights objects with latest info from bridge."""
+ """Update the lights objects with the latest info from the bridge."""
try:
- bridge.update_all_light_status()
- bridge.update_group_list()
+ new_lights = bridge.update_all_light_status(
+ config[CONF_INTERVAL_LIGHTIFY_STATUS])
+ lights_changed = bridge.lights_changed()
except TimeoutError:
_LOGGER.error("Timeout during updating of lights")
+ return 0
except OSError:
_LOGGER.error("OSError during updating of lights")
+ return 0
- new_lights = []
+ if new_lights and config[CONF_ALLOW_LIGHTIFY_NODES]:
+ new_entities = []
+ for addr, light in new_lights.items():
+ if ((light.devicetype().name == 'SENSOR'
+ and not config[CONF_ALLOW_LIGHTIFY_SENSORS]) or
+ (light.devicetype().name == 'SWITCH'
+ and not config[CONF_ALLOW_LIGHTIFY_SWITCHES])):
+ continue
- if add_nodes:
- for (light_id, light) in bridge.lights().items():
- if light_id not in lights:
- osram_light = OsramLightifyLight(
- light_id, light, update_lights)
- lights[light_id] = osram_light
- new_lights.append(osram_light)
+ if addr not in lights:
+ osram_light = OsramLightifyLight(light, update_lights,
+ lights_changed)
+ lights[addr] = osram_light
+ new_entities.append(osram_light)
else:
- lights[light_id].light = light
+ lights[addr].update_luminary(light)
- if add_groups:
- for (group_name, group) in bridge.groups().items():
- if group_name not in lights:
- osram_group = OsramLightifyGroup(
- group, bridge, update_lights)
- lights[group_name] = osram_group
- new_lights.append(osram_group)
+ add_entities(new_entities)
+
+ return lights_changed
+
+ def update_groups():
+ """Update the groups objects with the latest info from the bridge."""
+ lights_changed = update_lights()
+
+ try:
+ bridge.update_scene_list(config[CONF_INTERVAL_LIGHTIFY_CONF])
+ new_groups = bridge.update_group_list(
+ config[CONF_INTERVAL_LIGHTIFY_CONF])
+ groups_updated = bridge.groups_updated()
+ except TimeoutError:
+ _LOGGER.error("Timeout during updating of scenes/groups")
+ return 0
+ except OSError:
+ _LOGGER.error("OSError during updating of scenes/groups")
+ return 0
+
+ if new_groups:
+ new_groups = {group.idx(): group for group in new_groups.values()}
+ new_entities = []
+ for idx, group in new_groups.items():
+ if idx not in groups:
+ osram_group = OsramLightifyGroup(group, update_groups,
+ groups_updated)
+ groups[idx] = osram_group
+ new_entities.append(osram_group)
else:
- lights[group_name].group = group
+ groups[idx].update_luminary(group)
- if new_lights:
- add_entities(new_lights)
+ add_entities(new_entities)
+
+ if groups_updated > groups_last_updated[0]:
+ groups_last_updated[0] = groups_updated
+ for idx, osram_group in groups.items():
+ if idx not in new_groups:
+ osram_group.update_static_attributes()
+
+ return max(lights_changed, groups_updated)
update_lights()
+ if config[CONF_ALLOW_LIGHTIFY_GROUPS]:
+ update_groups()
class Luminary(Light):
"""Representation of Luminary Lights and Groups."""
- def __init__(self, luminary, update_lights):
- """Initialize a Luminary light."""
- self.update_lights = update_lights
+ def __init__(self, luminary, update_func, changed):
+ """Initialize a Luminary Light."""
+ self.update_func = update_func
self._luminary = luminary
+ self._changed = changed
+
+ self._unique_id = None
+ self._supported_features = []
+ self._effect_list = []
+ self._is_on = False
+ self._available = True
+ self._min_mireds = None
+ self._max_mireds = None
self._brightness = None
- self._hs = None
- self._name = None
- self._temperature = None
- self._state = False
- self.update()
+ self._color_temp = None
+ self._rgb_color = None
+ self._device_attributes = None
+
+ self.update_static_attributes()
+ self.update_dynamic_attributes()
+
+ def _get_unique_id(self):
+ """Get a unique ID (not implemented)."""
+ raise NotImplementedError
+
+ def _get_supported_features(self):
+ """Get list of supported features."""
+ features = 0
+ if 'lum' in self._luminary.supported_features():
+ features = features | SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
+
+ if 'temp' in self._luminary.supported_features():
+ features = features | SUPPORT_COLOR_TEMP | SUPPORT_TRANSITION
+
+ if 'rgb' in self._luminary.supported_features():
+ features = (features | SUPPORT_COLOR | SUPPORT_TRANSITION |
+ SUPPORT_EFFECT)
+
+ return features
+
+ def _get_effect_list(self):
+ """Get list of supported effects."""
+ effects = []
+ if 'rgb' in self._luminary.supported_features():
+ effects.append(EFFECT_RANDOM)
+
+ return effects
@property
def name(self):
- """Return the name of the device if any."""
- return self._name
+ """Return the name of the luminary."""
+ return self._luminary.name()
@property
def hs_color(self):
- """Last hs color value set."""
- return self._hs
+ """Return last hs color value set."""
+ return color_util.color_RGB_to_hs(*self._rgb_color)
@property
def color_temp(self):
"""Return the color temperature."""
- return self._temperature
+ return self._color_temp
@property
def brightness(self):
- """Brightness of this light between 0..255."""
+ """Return brightness of the luminary (0..255)."""
return self._brightness
@property
def is_on(self):
- """Update status to True if device is on."""
- return self._state
+ """Return True if the device is on."""
+ return self._is_on
@property
def supported_features(self):
- """Flag supported features."""
- return SUPPORT_OSRAMLIGHTIFY
+ """List of supported features."""
+ return self._supported_features
@property
def effect_list(self):
"""List of supported effects."""
- return [EFFECT_RANDOM]
-
- def turn_on(self, **kwargs):
- """Turn the device on."""
- if ATTR_TRANSITION in kwargs:
- transition = int(kwargs[ATTR_TRANSITION] * 10)
- else:
- transition = 0
-
- if ATTR_BRIGHTNESS in kwargs:
- self._brightness = kwargs[ATTR_BRIGHTNESS]
- self._luminary.set_luminance(
- int(self._brightness / 2.55), transition)
- else:
- self._luminary.set_onoff(1)
-
- if ATTR_HS_COLOR in kwargs:
- red, green, blue = \
- color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
- self._luminary.set_rgb(red, green, blue, transition)
-
- if ATTR_COLOR_TEMP in kwargs:
- color_t = kwargs[ATTR_COLOR_TEMP]
- kelvin = int(color_temperature_mired_to_kelvin(color_t))
- self._luminary.set_temperature(kelvin, transition)
-
- if ATTR_EFFECT in kwargs:
- effect = kwargs.get(ATTR_EFFECT)
- if effect == EFFECT_RANDOM:
- self._luminary.set_rgb(
- random.randrange(0, 255), random.randrange(0, 255),
- random.randrange(0, 255), transition)
-
- self.schedule_update_ha_state()
-
- def turn_off(self, **kwargs):
- """Turn the device off."""
- if ATTR_TRANSITION in kwargs:
- transition = int(kwargs[ATTR_TRANSITION] * 10)
- self._luminary.set_luminance(0, transition)
- else:
- transition = 0
- self._luminary.set_onoff(0)
- self.schedule_update_ha_state()
-
- def update(self):
- """Synchronize state with bridge."""
- self.update_lights(no_throttle=True)
- self._name = self._luminary.name()
-
-
-class OsramLightifyLight(Luminary):
- """Representation of an Osram Lightify Light."""
-
- def __init__(self, light_id, light, update_lights):
- """Initialize the Lightify light."""
- self._light_id = light_id
- super().__init__(light, update_lights)
-
- def update(self):
- """Update status of a light."""
- super().update()
- self._state = self._luminary.on()
- rgb = self._luminary.rgb()
- self._hs = color_util.color_RGB_to_hs(*rgb)
- o_temp = self._luminary.temp()
- if o_temp == 0:
- self._temperature = None
- else:
- self._temperature = color_temperature_kelvin_to_mired(
- self._luminary.temp())
- self._brightness = int(self._luminary.lum() * 2.55)
+ return self._effect_list
@property
- def unique_id(self):
- """Return a unique ID."""
- return self._light_id
+ def min_mireds(self):
+ """Return the coldest color_temp that this light supports."""
+ return self._min_mireds
-
-class OsramLightifyGroup(Luminary):
- """Representation of an Osram Lightify Group."""
-
- def __init__(self, group, bridge, update_lights):
- """Initialize the Lightify light group."""
- self._bridge = bridge
- self._light_ids = []
- super().__init__(group, update_lights)
- self._unique_id = '{}'.format(self._light_ids)
-
- def _get_state(self):
- """Get state of group."""
- lights = self._bridge.lights()
- return any(lights[light_id].on() for light_id in self._light_ids)
-
- def update(self):
- """Update group status."""
- super().update()
- self._light_ids = self._luminary.lights()
- light = self._bridge.lights()[self._light_ids[0]]
- self._brightness = int(light.lum() * 2.55)
- rgb = light.rgb()
- self._hs = color_util.color_RGB_to_hs(*rgb)
- o_temp = light.temp()
- if o_temp == 0:
- self._temperature = None
- else:
- self._temperature = color_temperature_kelvin_to_mired(o_temp)
- self._state = light.on()
+ @property
+ def max_mireds(self):
+ """Return the warmest color_temp that this light supports."""
+ return self._max_mireds
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
+
+ @property
+ def device_state_attributes(self):
+ """Return device specific state attributes."""
+ return self._device_attributes
+
+ @property
+ def available(self):
+ """Return True if entity is available."""
+ return self._available
+
+ def play_effect(self, effect, transition):
+ """Play selected effect."""
+ if effect == EFFECT_RANDOM:
+ self._rgb_color = (random.randrange(0, 256),
+ random.randrange(0, 256),
+ random.randrange(0, 256))
+ self._luminary.set_rgb(*self._rgb_color, transition)
+ self._luminary.set_onoff(True)
+ return True
+
+ return False
+
+ def turn_on(self, **kwargs):
+ """Turn the device on."""
+ transition = int(kwargs.get(ATTR_TRANSITION, 0) * 10)
+ if ATTR_EFFECT in kwargs:
+ self.play_effect(kwargs[ATTR_EFFECT], transition)
+ return
+
+ if ATTR_HS_COLOR in kwargs:
+ self._rgb_color = color_util.color_hs_to_RGB(
+ *kwargs[ATTR_HS_COLOR])
+ self._luminary.set_rgb(*self._rgb_color, transition)
+
+ if ATTR_COLOR_TEMP in kwargs:
+ self._color_temp = kwargs[ATTR_COLOR_TEMP]
+ self._luminary.set_temperature(
+ int(color_util.color_temperature_mired_to_kelvin(
+ self._color_temp)), transition)
+
+ self._is_on = True
+ if ATTR_BRIGHTNESS in kwargs:
+ self._brightness = kwargs[ATTR_BRIGHTNESS]
+ self._luminary.set_luminance(int(self._brightness / 2.55),
+ transition)
+ else:
+ self._luminary.set_onoff(True)
+
+ def turn_off(self, **kwargs):
+ """Turn the device off."""
+ self._is_on = False
+ if ATTR_TRANSITION in kwargs:
+ transition = int(kwargs[ATTR_TRANSITION] * 10)
+ self._brightness = DEFAULT_BRIGHTNESS
+ self._luminary.set_luminance(0, transition)
+ else:
+ self._luminary.set_onoff(False)
+
+ def update_luminary(self, luminary):
+ """Update internal luminary object."""
+ self._luminary = luminary
+ self.update_static_attributes()
+
+ def update_static_attributes(self):
+ """Update static attributes of the luminary."""
+ self._unique_id = self._get_unique_id()
+ self._supported_features = self._get_supported_features()
+ self._effect_list = self._get_effect_list()
+ if self._supported_features & SUPPORT_COLOR_TEMP:
+ self._min_mireds = color_util.color_temperature_kelvin_to_mired(
+ self._luminary.max_temp() or DEFAULT_KELVIN)
+ self._max_mireds = color_util.color_temperature_kelvin_to_mired(
+ self._luminary.min_temp() or DEFAULT_KELVIN)
+
+ def update_dynamic_attributes(self):
+ """Update dynamic attributes of the luminary."""
+ self._is_on = self._luminary.on()
+ self._available = (self._luminary.reachable() and
+ not self._luminary.deleted())
+ if self._supported_features & SUPPORT_BRIGHTNESS:
+ self._brightness = int(self._luminary.lum() * 2.55)
+
+ if self._supported_features & SUPPORT_COLOR_TEMP:
+ self._color_temp = color_util.color_temperature_kelvin_to_mired(
+ self._luminary.temp() or DEFAULT_KELVIN)
+
+ if self._supported_features & SUPPORT_COLOR:
+ self._rgb_color = self._luminary.rgb()
+
+ def update(self):
+ """Synchronize state with bridge."""
+ changed = self.update_func()
+ if changed > self._changed:
+ self._changed = changed
+ self.update_dynamic_attributes()
+
+
+class OsramLightifyLight(Luminary):
+ """Representation of an Osram Lightify Light."""
+
+ def _get_unique_id(self):
+ """Get a unique ID."""
+ return self._luminary.addr()
+
+ def update_static_attributes(self):
+ """Update static attributes of the luminary."""
+ super().update_static_attributes()
+ attrs = {'device_type': '{} ({})'.format(self._luminary.type_id(),
+ self._luminary.devicename()),
+ 'firmware_version': self._luminary.version()}
+ if self._luminary.devicetype().name == 'SENSOR':
+ attrs['sensor_values'] = self._luminary.raw_values()
+
+ self._device_attributes = attrs
+
+
+class OsramLightifyGroup(Luminary):
+ """Representation of an Osram Lightify Group."""
+
+ def _get_unique_id(self):
+ """Get a unique ID for the group."""
+# Actually, it's a wrong choice for a unique ID, because a combination of
+# lights is NOT unique (Osram Lightify allows to create different groups
+# with the same lights). Also a combination of lights may easily change,
+# but the group remains the same from the user's perspective.
+# It should be something like "-"
+# For now keeping it as is for backward compatibility with existing
+# users.
+ return '{}'.format(self._luminary.lights())
+
+ def _get_supported_features(self):
+ """Get list of supported features."""
+ features = super()._get_supported_features()
+ if self._luminary.scenes():
+ features = features | SUPPORT_EFFECT
+
+ return features
+
+ def _get_effect_list(self):
+ """Get list of supported effects."""
+ effects = super()._get_effect_list()
+ effects.extend(self._luminary.scenes())
+ return sorted(effects)
+
+ def play_effect(self, effect, transition):
+ """Play selected effect."""
+ if super().play_effect(effect, transition):
+ return True
+
+ if effect in self._luminary.scenes():
+ self._luminary.activate_scene(effect)
+ return True
+
+ return False
+
+ def update_static_attributes(self):
+ """Update static attributes of the luminary."""
+ super().update_static_attributes()
+ self._device_attributes = {'lights': self._luminary.light_names()}
diff --git a/homeassistant/components/osramlightify/manifest.json b/homeassistant/components/osramlightify/manifest.json
new file mode 100644
index 00000000000..0b158b96742
--- /dev/null
+++ b/homeassistant/components/osramlightify/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "osramlightify",
+ "name": "Osramlightify",
+ "documentation": "https://www.home-assistant.io/components/osramlightify",
+ "requirements": [
+ "lightify==1.0.7.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/otp/manifest.json b/homeassistant/components/otp/manifest.json
new file mode 100644
index 00000000000..3eb24e0f1c6
--- /dev/null
+++ b/homeassistant/components/otp/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "otp",
+ "name": "Otp",
+ "documentation": "https://www.home-assistant.io/components/otp",
+ "requirements": [
+ "pyotp==2.2.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/otp/sensor.py b/homeassistant/components/otp/sensor.py
index 5394b49c389..0f79955db15 100644
--- a/homeassistant/components/otp/sensor.py
+++ b/homeassistant/components/otp/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for One-Time Password (OTP).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.otp/
-"""
+"""Support for One-Time Password (OTP)."""
import time
import logging
@@ -15,8 +10,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_TOKEN)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyotp==2.2.6']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'OTP Sensor'
diff --git a/homeassistant/components/owlet/__init__.py b/homeassistant/components/owlet/__init__.py
index b7ad7ab9152..f19df6a3e38 100644
--- a/homeassistant/components/owlet/__init__.py
+++ b/homeassistant/components/owlet/__init__.py
@@ -11,8 +11,6 @@ from .const import (
SENSOR_BASE_STATION, SENSOR_HEART_RATE, SENSOR_MOVEMENT,
SENSOR_OXYGEN_LEVEL)
-REQUIREMENTS = ['pyowlet==1.0.2']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'owlet'
diff --git a/homeassistant/components/owlet/manifest.json b/homeassistant/components/owlet/manifest.json
new file mode 100644
index 00000000000..edc51dcc533
--- /dev/null
+++ b/homeassistant/components/owlet/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "owlet",
+ "name": "Owlet",
+ "documentation": "https://www.home-assistant.io/components/owlet",
+ "requirements": [
+ "pyowlet==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@oblogic7"
+ ]
+}
diff --git a/homeassistant/components/owntracks/.translations/da.json b/homeassistant/components/owntracks/.translations/da.json
index 7f4053f8ead..bc1328d57e4 100644
--- a/homeassistant/components/owntracks/.translations/da.json
+++ b/homeassistant/components/owntracks/.translations/da.json
@@ -4,7 +4,7 @@
"one_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning"
},
"create_entry": {
- "default": "\n\n P\u00e5 Android skal du \u00e5bne [OwnTracks applikationen]({android_url}), g\u00e5 til indstillinger -> forbindelse. Skift f\u00f8lgende indstillinger: \n - Tilstand: Privat HTTP\n - V\u00e6rt: {webhook_url}\n - Identifikation:\n - Brugernavn: ` ` \n - Enheds-id: ` ` \n\n P\u00e5 iOS skal du \u00e5bne [OwnTracks applikationen]({ios_url}), tryk p\u00e5 (i) ikonet \u00f8verst til venstre -> indstillinger. Skift f\u00f8lgende indstillinger: \n - Tilstand: HTTP\n - URL: {webhook_url}\n - Aktiver godkendelse \n - Bruger ID: ` ` \n\n {secret}\n \n Se [dokumentationen]({docs_url}) for at f\u00e5 flere oplysninger."
+ "default": "\n\nP\u00e5 Android skal du \u00e5bne [OwnTracks applikationen]({android_url}), g\u00e5 til indstillinger -> forbindelse. Skift f\u00f8lgende indstillinger: \n - Tilstand: Privat HTTP\n - V\u00e6rt: {webhook_url}\n - Identifikation:\n - Brugernavn: ` ` \n - Enheds-id: ` ` \n\nP\u00e5 iOS skal du \u00e5bne [OwnTracks applikationen]({ios_url}), tryk p\u00e5 (i) ikonet \u00f8verst til venstre -> indstillinger. Skift f\u00f8lgende indstillinger: \n - Tilstand: HTTP\n - URL: {webhook_url}\n - Aktiver godkendelse \n - Bruger ID: ` ` \n\n {secret}\n \n Se [dokumentationen]({docs_url}) for at f\u00e5 flere oplysninger."
},
"step": {
"user": {
diff --git a/homeassistant/components/owntracks/.translations/fr.json b/homeassistant/components/owntracks/.translations/fr.json
index 46a0f2f2921..5975c34e78d 100644
--- a/homeassistant/components/owntracks/.translations/fr.json
+++ b/homeassistant/components/owntracks/.translations/fr.json
@@ -3,6 +3,9 @@
"abort": {
"one_instance_allowed": "Une seule instance est n\u00e9cessaire."
},
+ "create_entry": {
+ "default": "\n\n Sous Android, ouvrez [l'application OwnTracks] ( {android_url} ), acc\u00e9dez \u00e0 Pr\u00e9f\u00e9rences - > Connexion. Modifiez les param\u00e8tres suivants: \n - Mode: HTTP priv\u00e9 \n - H\u00f4te: {webhook_url} \n - Identification: \n - Nom d'utilisateur: ` ` \n - ID de p\u00e9riph\u00e9rique: ` ` \n\n Sur iOS, ouvrez [l'application OwnTracks] ( {ios_url} ), appuyez sur l'ic\u00f4ne (i) en haut \u00e0 gauche - > param\u00e8tres. Modifiez les param\u00e8tres suivants: \n - Mode: HTTP \n - URL: {webhook_url} \n - Activer l'authentification \n - ID utilisateur: ` ` \n\n {secret} \n \n Voir [la documentation] ( {docs_url} ) pour plus d'informations."
+ },
"step": {
"user": {
"description": "\u00cates-vous s\u00fbr de vouloir configurer OwnTracks?",
diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py
index df6b815e4c5..e746cbc01fa 100644
--- a/homeassistant/components/owntracks/__init__.py
+++ b/homeassistant/components/owntracks/__init__.py
@@ -16,13 +16,9 @@ from homeassistant.setup import async_when_setup
from .config_flow import CONF_SECRET
-REQUIREMENTS = ['PyNaCl==1.3.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'owntracks'
-DEPENDENCIES = ['webhook']
-
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py
index f1214b62b0e..999e883be19 100644
--- a/homeassistant/components/owntracks/device_tracker.py
+++ b/homeassistant/components/owntracks/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Device tracker platform that adds support for OwnTracks over MQTT.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.owntracks/
-"""
+"""Device tracker platform that adds support for OwnTracks over MQTT."""
import json
import logging
@@ -15,8 +10,6 @@ from homeassistant.util import decorator, slugify
from . import DOMAIN as OT_DOMAIN
-DEPENDENCIES = ['owntracks']
-
_LOGGER = logging.getLogger(__name__)
HANDLERS = decorator.Registry()
diff --git a/homeassistant/components/owntracks/manifest.json b/homeassistant/components/owntracks/manifest.json
new file mode 100644
index 00000000000..60bce1bca3d
--- /dev/null
+++ b/homeassistant/components/owntracks/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "owntracks",
+ "name": "Owntracks",
+ "documentation": "https://www.home-assistant.io/components/owntracks",
+ "requirements": [
+ "PyNaCl==1.3.0"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "after_dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/panasonic_bluray/manifest.json b/homeassistant/components/panasonic_bluray/manifest.json
new file mode 100644
index 00000000000..fe2387744ab
--- /dev/null
+++ b/homeassistant/components/panasonic_bluray/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "panasonic_bluray",
+ "name": "Panasonic bluray",
+ "documentation": "https://www.home-assistant.io/components/panasonic_bluray",
+ "requirements": [
+ "panacotta==0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/panasonic_bluray/media_player.py b/homeassistant/components/panasonic_bluray/media_player.py
index 36a3160d3b5..9da5cf87e53 100644
--- a/homeassistant/components/panasonic_bluray/media_player.py
+++ b/homeassistant/components/panasonic_bluray/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Panasonic Blu-Ray players.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/panasonic_bluray/
-"""
+"""Support for Panasonic Blu-Ray players."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['panacotta==0.1']
-
DEFAULT_NAME = "Panasonic Blu-Ray"
SCAN_INTERVAL = timedelta(seconds=30)
diff --git a/homeassistant/components/panasonic_viera/manifest.json b/homeassistant/components/panasonic_viera/manifest.json
new file mode 100644
index 00000000000..432e729ef20
--- /dev/null
+++ b/homeassistant/components/panasonic_viera/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "panasonic_viera",
+ "name": "Panasonic viera",
+ "documentation": "https://www.home-assistant.io/components/panasonic_viera",
+ "requirements": [
+ "panasonic_viera==0.3.2",
+ "wakeonlan==1.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py
index f1ac0cd90d4..4669d4ecac6 100644
--- a/homeassistant/components/panasonic_viera/media_player.py
+++ b/homeassistant/components/panasonic_viera/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with a Panasonic Viera TV.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.panasonic_viera/
-"""
+"""Support for interface with a Panasonic Viera TV."""
import logging
import voluptuous as vol
@@ -19,8 +14,6 @@ from homeassistant.const import (
CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['panasonic_viera==0.3.2', 'wakeonlan==1.1.6']
-
_LOGGER = logging.getLogger(__name__)
CONF_APP_POWER = 'app_power'
diff --git a/homeassistant/components/pandora/manifest.json b/homeassistant/components/pandora/manifest.json
new file mode 100644
index 00000000000..68e8337a33d
--- /dev/null
+++ b/homeassistant/components/pandora/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pandora",
+ "name": "Pandora",
+ "documentation": "https://www.home-assistant.io/components/pandora",
+ "requirements": [
+ "pexpect==4.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pandora/media_player.py b/homeassistant/components/pandora/media_player.py
index ca78f7a4318..14eb260914a 100644
--- a/homeassistant/components/pandora/media_player.py
+++ b/homeassistant/components/pandora/media_player.py
@@ -1,9 +1,4 @@
-"""
-Component for controlling Pandora stations through the pianobar client.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/media_player.pandora/
-"""
+"""Component for controlling Pandora stations through the pianobar client."""
from datetime import timedelta
import logging
import os
@@ -21,7 +16,6 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_UP,
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
-REQUIREMENTS = ['pexpect==4.6.0']
_LOGGER = logging.getLogger(__name__)
# SUPPORT_VOLUME_SET is close to available but we need volume up/down
diff --git a/homeassistant/components/panel_custom/__init__.py b/homeassistant/components/panel_custom/__init__.py
index 7fe2191f4c4..f6a4fcdb733 100644
--- a/homeassistant/components/panel_custom/__init__.py
+++ b/homeassistant/components/panel_custom/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'panel_custom'
-DEPENDENCIES = ['frontend']
-
CONF_COMPONENT_NAME = 'name'
CONF_SIDEBAR_TITLE = 'sidebar_title'
CONF_SIDEBAR_ICON = 'sidebar_icon'
@@ -126,9 +124,12 @@ async def async_register_panel(
async def async_setup(hass, config):
"""Initialize custom panel."""
+ if DOMAIN not in config:
+ return True
+
success = False
- for panel in config.get(DOMAIN):
+ for panel in config[DOMAIN]:
name = panel[CONF_COMPONENT_NAME]
kwargs = {
diff --git a/homeassistant/components/panel_custom/manifest.json b/homeassistant/components/panel_custom/manifest.json
new file mode 100644
index 00000000000..5fb7adb2a4a
--- /dev/null
+++ b/homeassistant/components/panel_custom/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "panel_custom",
+ "name": "Panel custom",
+ "documentation": "https://www.home-assistant.io/components/panel_custom",
+ "requirements": [],
+ "dependencies": [
+ "frontend"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/panel_iframe/__init__.py b/homeassistant/components/panel_iframe/__init__.py
index 9319dfcc6ad..f4038c82f71 100644
--- a/homeassistant/components/panel_iframe/__init__.py
+++ b/homeassistant/components/panel_iframe/__init__.py
@@ -4,8 +4,6 @@ import voluptuous as vol
from homeassistant.const import CONF_ICON, CONF_URL
import homeassistant.helpers.config_validation as cv
-DEPENDENCIES = ['frontend']
-
DOMAIN = 'panel_iframe'
CONF_TITLE = 'title'
diff --git a/homeassistant/components/panel_iframe/manifest.json b/homeassistant/components/panel_iframe/manifest.json
new file mode 100644
index 00000000000..127ff3caa4d
--- /dev/null
+++ b/homeassistant/components/panel_iframe/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "panel_iframe",
+ "name": "Panel iframe",
+ "documentation": "https://www.home-assistant.io/components/panel_iframe",
+ "requirements": [],
+ "dependencies": [
+ "frontend"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/pencom/manifest.json b/homeassistant/components/pencom/manifest.json
new file mode 100644
index 00000000000..186e071d25b
--- /dev/null
+++ b/homeassistant/components/pencom/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pencom",
+ "name": "Pencom",
+ "documentation": "https://www.home-assistant.io/components/pencom",
+ "requirements": [
+ "pencompy==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pencom/switch.py b/homeassistant/components/pencom/switch.py
index d2c73d70d96..3fc65e73770 100644
--- a/homeassistant/components/pencom/switch.py
+++ b/homeassistant/components/pencom/switch.py
@@ -12,8 +12,6 @@ from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pencompy==0.0.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_BOARDS = 'boards'
diff --git a/homeassistant/components/persistent_notification/manifest.json b/homeassistant/components/persistent_notification/manifest.json
new file mode 100644
index 00000000000..8bc343e1f08
--- /dev/null
+++ b/homeassistant/components/persistent_notification/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "persistent_notification",
+ "name": "Persistent notification",
+ "documentation": "https://www.home-assistant.io/components/persistent_notification",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py
index e6f83b80ba4..89fac761497 100644
--- a/homeassistant/components/person/__init__.py
+++ b/homeassistant/components/person/__init__.py
@@ -50,7 +50,8 @@ PERSON_SCHEMA = vol.Schema({
})
CONFIG_SCHEMA = vol.Schema({
- vol.Optional(DOMAIN): vol.Any(vol.All(cv.ensure_list, [PERSON_SCHEMA]), {})
+ vol.Optional(DOMAIN): vol.All(
+ cv.ensure_list, cv.remove_falsy, [PERSON_SCHEMA])
}, extra=vol.ALLOW_EXTRA)
_UNDEF = object()
diff --git a/homeassistant/components/person/manifest.json b/homeassistant/components/person/manifest.json
new file mode 100644
index 00000000000..d2cba929259
--- /dev/null
+++ b/homeassistant/components/person/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "person",
+ "name": "Person",
+ "documentation": "https://www.home-assistant.io/components/person",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json
new file mode 100644
index 00000000000..18ddcf1f5ff
--- /dev/null
+++ b/homeassistant/components/philips_js/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "philips_js",
+ "name": "Philips js",
+ "documentation": "https://www.home-assistant.io/components/philips_js",
+ "requirements": [
+ "ha-philipsjs==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py
index 97ec758e6cf..859ad26a3dd 100644
--- a/homeassistant/components/philips_js/media_player.py
+++ b/homeassistant/components/philips_js/media_player.py
@@ -1,9 +1,4 @@
-"""
-Media Player component to integrate TVs exposing the Joint Space API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.philips_js/
-"""
+"""Media Player component to integrate TVs exposing the Joint Space API."""
from datetime import timedelta
import logging
@@ -20,8 +15,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script
-REQUIREMENTS = ['ha-philipsjs==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=30)
diff --git a/homeassistant/components/pi_hole/manifest.json b/homeassistant/components/pi_hole/manifest.json
new file mode 100644
index 00000000000..c47d8811e68
--- /dev/null
+++ b/homeassistant/components/pi_hole/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "pi_hole",
+ "name": "Pi hole",
+ "documentation": "https://www.home-assistant.io/components/pi_hole",
+ "requirements": [
+ "hole==0.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py
index ae9aca5bc79..061fb5c091f 100644
--- a/homeassistant/components/pi_hole/sensor.py
+++ b/homeassistant/components/pi_hole/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for getting statistical data from a Pi-hole system.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.pi_hole/
-"""
+"""Support for getting statistical data from a Pi-hole system."""
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['hole==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_BLOCKED_DOMAINS = 'domains_blocked'
diff --git a/homeassistant/components/picotts/manifest.json b/homeassistant/components/picotts/manifest.json
new file mode 100644
index 00000000000..bfe7f449ca0
--- /dev/null
+++ b/homeassistant/components/picotts/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "picotts",
+ "name": "Picotts",
+ "documentation": "https://www.home-assistant.io/components/picotts",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/picotts/tts.py b/homeassistant/components/picotts/tts.py
index c164e7fb85d..fffadae0f13 100644
--- a/homeassistant/components/picotts/tts.py
+++ b/homeassistant/components/picotts/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the Pico TTS speech service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts.picotts/
-"""
+"""Support for the Pico TTS speech service."""
import logging
import os
import shutil
diff --git a/homeassistant/components/piglow/light.py b/homeassistant/components/piglow/light.py
index 56c72e01fdf..52e5c769560 100644
--- a/homeassistant/components/piglow/light.py
+++ b/homeassistant/components/piglow/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Piglow LED's.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.piglow/
-"""
+"""Support for Piglow LED's."""
import logging
import subprocess
@@ -16,8 +11,6 @@ from homeassistant.components.light import (
from homeassistant.const import CONF_NAME
import homeassistant.util.color as color_util
-REQUIREMENTS = ['piglow==1.2.4']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_PIGLOW = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
diff --git a/homeassistant/components/piglow/manifest.json b/homeassistant/components/piglow/manifest.json
new file mode 100644
index 00000000000..67b1033c51e
--- /dev/null
+++ b/homeassistant/components/piglow/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "piglow",
+ "name": "Piglow",
+ "documentation": "https://www.home-assistant.io/components/piglow",
+ "requirements": [
+ "piglow==1.2.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pilight/__init__.py b/homeassistant/components/pilight/__init__.py
index d307a428e0e..b6f1a63d4d5 100644
--- a/homeassistant/components/pilight/__init__.py
+++ b/homeassistant/components/pilight/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to create an interface to a Pilight daemon.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/pilight/
-"""
+"""Component to create an interface to a Pilight daemon."""
import logging
import functools
import socket
@@ -19,8 +14,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT,
CONF_WHITELIST, CONF_PROTOCOL)
-REQUIREMENTS = ['pilight==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_SEND_DELAY = 'send_delay'
diff --git a/homeassistant/components/pilight/binary_sensor.py b/homeassistant/components/pilight/binary_sensor.py
index de23baef884..b9e95f76c49 100644
--- a/homeassistant/components/pilight/binary_sensor.py
+++ b/homeassistant/components/pilight/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Pilight binary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.pilight/
-"""
+"""Support for Pilight binary sensors."""
import datetime
import logging
@@ -31,8 +26,6 @@ CONF_VARIABLE = 'variable'
CONF_RESET_DELAY_SEC = 'reset_delay_sec'
DEFAULT_NAME = 'Pilight Binary Sensor'
-DEPENDENCIES = ['pilight']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_VARIABLE): cv.string,
vol.Required(CONF_PAYLOAD): vol.Schema(dict),
diff --git a/homeassistant/components/pilight/manifest.json b/homeassistant/components/pilight/manifest.json
new file mode 100644
index 00000000000..dfe4952e1a1
--- /dev/null
+++ b/homeassistant/components/pilight/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pilight",
+ "name": "Pilight",
+ "documentation": "https://www.home-assistant.io/components/pilight",
+ "requirements": [
+ "pilight==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pilight/sensor.py b/homeassistant/components/pilight/sensor.py
index ddcbe018f8e..a6be0f67f7c 100644
--- a/homeassistant/components/pilight/sensor.py
+++ b/homeassistant/components/pilight/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Pilight sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.pilight/
-"""
+"""Support for Pilight sensors."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_VARIABLE = 'variable'
DEFAULT_NAME = 'Pilight Sensor'
-DEPENDENCIES = ['pilight']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_VARIABLE): cv.string,
vol.Required(CONF_PAYLOAD): vol.Schema(dict),
diff --git a/homeassistant/components/pilight/services.yaml b/homeassistant/components/pilight/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/pilight/switch.py b/homeassistant/components/pilight/switch.py
index 3bbe2e69110..2f28e7f4d8a 100644
--- a/homeassistant/components/pilight/switch.py
+++ b/homeassistant/components/pilight/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for switching devices via Pilight to on and off.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.pilight/
-"""
+"""Support for switching devices via Pilight to on and off."""
import logging
import voluptuous as vol
@@ -26,8 +21,6 @@ CONF_UNIT = 'unit'
CONF_UNITCODE = 'unitcode'
CONF_ECHO = 'echo'
-DEPENDENCIES = ['pilight']
-
COMMAND_SCHEMA = vol.Schema({
vol.Optional(CONF_PROTOCOL): cv.string,
vol.Optional('on'): cv.positive_int,
diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py
index 4c597dd63e1..4f95a470efb 100644
--- a/homeassistant/components/ping/binary_sensor.py
+++ b/homeassistant/components/ping/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Tracks the latency of a host by sending ICMP echo requests (ping).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.ping/
-"""
+"""Tracks the latency of a host by sending ICMP echo requests (ping)."""
import logging
import subprocess
import re
diff --git a/homeassistant/components/ping/device_tracker.py b/homeassistant/components/ping/device_tracker.py
index f3492da9e80..9f9bf4475b4 100644
--- a/homeassistant/components/ping/device_tracker.py
+++ b/homeassistant/components/ping/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Tracks devices by sending a ICMP echo request (ping).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.ping/
-"""
+"""Tracks devices by sending a ICMP echo request (ping)."""
import logging
import subprocess
import sys
diff --git a/homeassistant/components/ping/manifest.json b/homeassistant/components/ping/manifest.json
new file mode 100644
index 00000000000..d98adef87a7
--- /dev/null
+++ b/homeassistant/components/ping/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "ping",
+ "name": "Ping",
+ "documentation": "https://www.home-assistant.io/components/ping",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pioneer/manifest.json b/homeassistant/components/pioneer/manifest.json
new file mode 100644
index 00000000000..b06874149ed
--- /dev/null
+++ b/homeassistant/components/pioneer/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "pioneer",
+ "name": "Pioneer",
+ "documentation": "https://www.home-assistant.io/components/pioneer",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pioneer/media_player.py b/homeassistant/components/pioneer/media_player.py
index 00fa453100a..a687ba5ad4a 100644
--- a/homeassistant/components/pioneer/media_player.py
+++ b/homeassistant/components/pioneer/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Pioneer Network Receivers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.pioneer/
-"""
+"""Support for Pioneer Network Receivers."""
import logging
import telnetlib
diff --git a/homeassistant/components/pjlink/manifest.json b/homeassistant/components/pjlink/manifest.json
new file mode 100644
index 00000000000..6901847bd8d
--- /dev/null
+++ b/homeassistant/components/pjlink/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pjlink",
+ "name": "Pjlink",
+ "documentation": "https://www.home-assistant.io/components/pjlink",
+ "requirements": [
+ "pypjlink2==1.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pjlink/media_player.py b/homeassistant/components/pjlink/media_player.py
index c1b883a0295..00a4d49bd5c 100644
--- a/homeassistant/components/pjlink/media_player.py
+++ b/homeassistant/components/pjlink/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for controlling projector via the PJLink protocol.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.pjlink/
-"""
+"""Support for controlling projector via the PJLink protocol."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pypjlink2==1.2.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_ENCODING = 'encoding'
diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py
index 27324ad57a3..78f979892b1 100644
--- a/homeassistant/components/plant/__init__.py
+++ b/homeassistant/components/plant/__init__.py
@@ -89,8 +89,6 @@ PLANT_SCHEMA = vol.Schema({
})
DOMAIN = 'plant'
-DEPENDENCIES = ['zone', 'group']
-
GROUP_NAME_ALL_PLANTS = 'all plants'
ENTITY_ID_ALL_PLANTS = group.ENTITY_ID_FORMAT.format('all_plants')
diff --git a/homeassistant/components/plant/manifest.json b/homeassistant/components/plant/manifest.json
new file mode 100644
index 00000000000..cbde894173b
--- /dev/null
+++ b/homeassistant/components/plant/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "plant",
+ "name": "Plant",
+ "documentation": "https://www.home-assistant.io/components/plant",
+ "requirements": [],
+ "dependencies": [
+ "group",
+ "zone"
+ ],
+ "codeowners": [
+ "@ChristianKuehnel"
+ ]
+}
diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json
new file mode 100644
index 00000000000..32ddb83476c
--- /dev/null
+++ b/homeassistant/components/plex/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "plex",
+ "name": "Plex",
+ "documentation": "https://www.home-assistant.io/components/plex",
+ "requirements": [
+ "plexapi==3.0.6"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py
index a68a2faade8..4cb4204f274 100644
--- a/homeassistant/components/plex/media_player.py
+++ b/homeassistant/components/plex/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support to interface with the Plex API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.plex/
-"""
+"""Support to interface with the Plex API."""
from datetime import timedelta
import json
import logging
@@ -25,8 +20,6 @@ from homeassistant.helpers.event import track_utc_time_change
from homeassistant.util import dt as dt_util
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['plexapi==3.0.6']
-
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
@@ -679,7 +672,7 @@ class PlexClient(MediaPlayerDevice):
def supported_features(self):
"""Flag media player features that are supported."""
if not self._is_player_active:
- return None
+ return 0
# force show all controls
if self.config.get(CONF_SHOW_ALL_CONTROLS):
@@ -690,7 +683,7 @@ class PlexClient(MediaPlayerDevice):
# only show controls when we know what device is connecting
if not self._make:
- return None
+ return 0
# no mute support
if self.make.lower() == "shield android tv":
_LOGGER.debug(
@@ -715,7 +708,7 @@ class PlexClient(MediaPlayerDevice):
SUPPORT_VOLUME_SET | SUPPORT_PLAY |
SUPPORT_TURN_OFF | SUPPORT_VOLUME_MUTE)
- return None
+ return 0
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py
index eaf73ceb566..4f46113347d 100644
--- a/homeassistant/components/plex/sensor.py
+++ b/homeassistant/components/plex/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Plex media server monitoring.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.plex/
-"""
+"""Support for Plex media server monitoring."""
from datetime import timedelta
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['plexapi==3.0.6']
-
_LOGGER = logging.getLogger(__name__)
CONF_SERVER = 'server'
diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py
index 5b99223d25a..b08727e7acc 100644
--- a/homeassistant/components/plum_lightpad/__init__.py
+++ b/homeassistant/components/plum_lightpad/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['plumlightpad==0.0.11']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'plum_lightpad'
diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py
index 233539560f4..8923d3c5acc 100644
--- a/homeassistant/components/plum_lightpad/light.py
+++ b/homeassistant/components/plum_lightpad/light.py
@@ -5,8 +5,6 @@ import homeassistant.util.color as color_util
from . import PLUM_DATA
-DEPENDENCIES = ['plum_lightpad']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/plum_lightpad/manifest.json b/homeassistant/components/plum_lightpad/manifest.json
new file mode 100644
index 00000000000..389eca09c42
--- /dev/null
+++ b/homeassistant/components/plum_lightpad/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "plum_lightpad",
+ "name": "Plum lightpad",
+ "documentation": "https://www.home-assistant.io/components/plum_lightpad",
+ "requirements": [
+ "plumlightpad==0.0.11"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pocketcasts/manifest.json b/homeassistant/components/pocketcasts/manifest.json
new file mode 100644
index 00000000000..11c20236324
--- /dev/null
+++ b/homeassistant/components/pocketcasts/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pocketcasts",
+ "name": "Pocketcasts",
+ "documentation": "https://www.home-assistant.io/components/pocketcasts",
+ "requirements": [
+ "pocketcasts==0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pocketcasts/sensor.py b/homeassistant/components/pocketcasts/sensor.py
index 9d5b837bba9..69d863cb9e9 100644
--- a/homeassistant/components/pocketcasts/sensor.py
+++ b/homeassistant/components/pocketcasts/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Pocket Casts.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.pocketcasts/
-"""
+"""Support for Pocket Casts."""
import logging
from datetime import timedelta
@@ -15,8 +10,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pocketcasts==0.1']
-
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:rss'
diff --git a/homeassistant/components/point/.translations/ca.json b/homeassistant/components/point/.translations/ca.json
index b50a1169a53..789068a6339 100644
--- a/homeassistant/components/point/.translations/ca.json
+++ b/homeassistant/components/point/.translations/ca.json
@@ -4,14 +4,14 @@
"already_setup": "Nom\u00e9s pots configurar un compte de Point.",
"authorize_url_fail": "S'ha produ\u00eft un error desconegut al generar l'URL d'autoritzaci\u00f3.",
"authorize_url_timeout": "S'ha acabat el temps d'espera mentre \u00e9s generava l'URL d'autoritzaci\u00f3.",
- "external_setup": "Point s'ha configurat correctament des d'un altre lloc.",
+ "external_setup": "Point s'ha configurat correctament des d'un altre flux de dades.",
"no_flows": "Necessites configurar Point abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/point/)."
},
"create_entry": {
"default": "Autenticaci\u00f3 exitosa amb Minut per als teus dispositiu/s Point."
},
"error": {
- "follow_link": "Si us plau v\u00e9s a l'enlla\u00e7 i autentica't abans de pr\u00e9mer Enviar",
+ "follow_link": "V\u00e9s a l'enlla\u00e7 i autentica't abans de pr\u00e9mer Enviar",
"no_token": "No s'ha autenticat amb Minut"
},
"step": {
@@ -23,7 +23,7 @@
"data": {
"flow_impl": "Prove\u00efdor"
},
- "description": "Tria a trav\u00e9s de quin prove\u00efdor d'autenticaci\u00f3 vols autenticar-te amb Point.",
+ "description": "Tria quin prove\u00efdor d'autenticaci\u00f3 vols utilitzar per autenticar-te amb Point.",
"title": "Prove\u00efdor d'autenticaci\u00f3"
}
},
diff --git a/homeassistant/components/point/.translations/es.json b/homeassistant/components/point/.translations/es.json
index 815f8fbf9af..1d092c28b64 100644
--- a/homeassistant/components/point/.translations/es.json
+++ b/homeassistant/components/point/.translations/es.json
@@ -1,13 +1,29 @@
{
"config": {
"abort": {
- "already_setup": "S\u00f3lo se puede configurar una cuenta de Point."
+ "already_setup": "S\u00f3lo se puede configurar una cuenta de Point.",
+ "authorize_url_fail": "Error desconocido generando la url de autorizaci\u00f3n",
+ "external_setup": "Point se ha configurado correctamente a partir de otro flujo.",
+ "no_flows": "Es necesario configurar Point antes de poder autenticarse con \u00e9l. [Echa un vistazo a las instrucciones] (https://www.home-assistant.io/components/point/)."
+ },
+ "create_entry": {
+ "default": "Autenticado correctamente con Minut para tu(s) dispositivo(s) Point"
+ },
+ "error": {
+ "follow_link": "Accede al enlace e identif\u00edcate antes de pulsar Enviar.",
+ "no_token": "No autenticado con Minut"
},
"step": {
+ "auth": {
+ "description": "Accede al siguiente enlace y Acepta el acceso a tu cuenta Minut, despu\u00e9s vuelve y pulsa en Enviar a continuaci\u00f3n.\n\n[Link]({authorization_url})",
+ "title": "Autenticaci\u00f3n con Point"
+ },
"user": {
"data": {
"flow_impl": "Proveedor"
- }
+ },
+ "description": "Elige a trav\u00e9s de qu\u00e9 proveedor de autenticaci\u00f3n quieres autenticarte con Point.",
+ "title": "Proveedor de autenticaci\u00f3n"
}
}
}
diff --git a/homeassistant/components/point/.translations/fr.json b/homeassistant/components/point/.translations/fr.json
index ba1b1e27668..c20b62ef3b6 100644
--- a/homeassistant/components/point/.translations/fr.json
+++ b/homeassistant/components/point/.translations/fr.json
@@ -3,9 +3,22 @@
"abort": {
"already_setup": "Vous ne pouvez configurer qu'un compte Point.",
"authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.",
- "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9."
+ "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.",
+ "external_setup": "Point correctement configur\u00e9 \u00e0 partir d\u2019un autre flux.",
+ "no_flows": "Vous devez configurer Point avant de pouvoir vous authentifier avec celui-ci. [Veuillez lire les instructions] (https://www.home-assistant.io/components/point/)."
+ },
+ "create_entry": {
+ "default": "Authentification r\u00e9ussie avec Minut pour votre (vos) p\u00e9riph\u00e9rique (s) Point"
+ },
+ "error": {
+ "follow_link": "Veuillez suivre le lien et vous authentifier avant d'appuyer sur Soumettre.",
+ "no_token": "Non authentifi\u00e9 avec Minut"
},
"step": {
+ "auth": {
+ "description": "Suivez le lien ci-dessous et acceptez l'acc\u00e8s \u00e0 votre compte Minut, puis revenez et appuyez sur Envoyer ci-dessous. \n\n [Lien] ( {authorization_url} )",
+ "title": "Point d'authentification"
+ },
"user": {
"data": {
"flow_impl": "Fournisseur"
diff --git a/homeassistant/components/point/.translations/ko.json b/homeassistant/components/point/.translations/ko.json
index 0480b6d7195..d70859c8bde 100644
--- a/homeassistant/components/point/.translations/ko.json
+++ b/homeassistant/components/point/.translations/ko.json
@@ -5,10 +5,10 @@
"authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
"authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"external_setup": "Point \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.",
- "no_flows": "Point \ub97c \uc778\uc99d\ud558\uae30 \uc804\uc5d0 Point \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/point/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694."
+ "no_flows": "Point \ub97c \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Point \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/point/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694."
},
"create_entry": {
- "default": "Point \uc7a5\uce58\ub294 Minut \ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
+ "default": "Point \uae30\uae30\ub294 Minut \ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
},
"error": {
"follow_link": "Submit \ubc84\ud2bc\uc744 \ub204\ub974\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694",
@@ -23,7 +23,7 @@
"data": {
"flow_impl": "\uacf5\uae09\uc790"
},
- "description": "Point\ub85c \uc778\uc99d\ud558\ub824\ub294 \uc778\uc99d \uacf5\uae09\uc790\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.",
+ "description": "Point \ub97c \uc778\uc99d\ud558\uae30 \uc704\ud55c \uc778\uc99d \uacf5\uae09\uc790\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.",
"title": "\uc778\uc99d \uacf5\uae09\uc790"
}
},
diff --git a/homeassistant/components/point/.translations/lb.json b/homeassistant/components/point/.translations/lb.json
index 571f4617215..ea589a2c3d3 100644
--- a/homeassistant/components/point/.translations/lb.json
+++ b/homeassistant/components/point/.translations/lb.json
@@ -16,7 +16,7 @@
},
"step": {
"auth": {
- "description": "Follegt dem Link \u00ebnnendr\u00ebnner an accept\u00e9iert den Acc\u00e8s zu \u00e4rem Minut Kont , dann kommt zer\u00e9ck heihin an dr\u00e9ck op ofsch\u00e9cken hei \u00ebnnen.\n\n[Link]({authorization_url})",
+ "description": "Follegt dem Link \u00ebnnendr\u00ebnner an accept\u00e9iert den Acc\u00e8s zu \u00e4rem Minut Kont , a kommt dann zer\u00e9ck heihin an dr\u00e9ck op ofsch\u00e9cken hei \u00ebnnen.\n\n[Link]({authorization_url})",
"title": "Point authentifiz\u00e9ieren"
},
"user": {
diff --git a/homeassistant/components/point/.translations/ru.json b/homeassistant/components/point/.translations/ru.json
index d2f3f90cb77..3e2bbc4df65 100644
--- a/homeassistant/components/point/.translations/ru.json
+++ b/homeassistant/components/point/.translations/ru.json
@@ -5,25 +5,25 @@
"authorize_url_fail": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
"authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
"external_setup": "Point \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430.",
- "no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Point \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/point/)."
+ "no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Point \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/point/)."
},
"create_entry": {
- "default": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e"
+ "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e."
},
"error": {
- "follow_link": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0438 \u043f\u0440\u043e\u0439\u0434\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u044c \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c",
- "no_token": "\u041d\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0432\u0445\u043e\u0434 \u0432 Minut"
+ "follow_link": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0438 \u043f\u0440\u043e\u0439\u0434\u0438\u0442\u0435 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u044c \"\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c\".",
+ "no_token": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430."
},
"step": {
"auth": {
"description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Minut, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c.",
- "title": "\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0432\u0445\u043e\u0434 \u0432 Point"
+ "title": "Minut Point"
},
"user": {
"data": {
"flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440"
},
- "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u043a\u043e\u0433\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0432\u0445\u043e\u0434 \u0432 Point.",
+ "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0432\u0445\u043e\u0434.",
"title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438"
}
},
diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py
index dc839756469..c0b2f7acd0f 100644
--- a/homeassistant/components/point/__init__.py
+++ b/homeassistant/components/point/__init__.py
@@ -20,12 +20,8 @@ from .const import (
CONF_WEBHOOK_URL, DOMAIN, EVENT_RECEIVED, POINT_DISCOVERY_NEW,
SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, SIGNAL_WEBHOOK)
-REQUIREMENTS = ['pypoint==1.1.1']
-
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['webhook']
-
CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret'
diff --git a/homeassistant/components/point/manifest.json b/homeassistant/components/point/manifest.json
new file mode 100644
index 00000000000..8b888a3647a
--- /dev/null
+++ b/homeassistant/components/point/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "point",
+ "name": "Point",
+ "documentation": "https://www.home-assistant.io/components/point",
+ "requirements": [
+ "pypoint==1.1.1"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": [
+ "@fredrike"
+ ]
+}
diff --git a/homeassistant/components/pollen/manifest.json b/homeassistant/components/pollen/manifest.json
new file mode 100644
index 00000000000..2edf83a0d1f
--- /dev/null
+++ b/homeassistant/components/pollen/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "pollen",
+ "name": "Pollen",
+ "documentation": "https://www.home-assistant.io/components/pollen",
+ "requirements": [
+ "numpy==1.16.2",
+ "pypollencom==2.2.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/pollen/sensor.py b/homeassistant/components/pollen/sensor.py
index 3fc4d1fce3d..132155c7f65 100644
--- a/homeassistant/components/pollen/sensor.py
+++ b/homeassistant/components/pollen/sensor.py
@@ -13,8 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['numpy==1.16.2', 'pypollencom==2.2.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ALLERGEN_AMOUNT = 'allergen_amount'
diff --git a/homeassistant/components/postnl/manifest.json b/homeassistant/components/postnl/manifest.json
new file mode 100644
index 00000000000..9746cb168aa
--- /dev/null
+++ b/homeassistant/components/postnl/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "postnl",
+ "name": "Postnl",
+ "documentation": "https://www.home-assistant.io/components/postnl",
+ "requirements": [
+ "postnl_api==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/postnl/sensor.py b/homeassistant/components/postnl/sensor.py
index 84cb42c0957..d2380748c79 100644
--- a/homeassistant/components/postnl/sensor.py
+++ b/homeassistant/components/postnl/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for PostNL packages.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.postnl/
-"""
+"""Sensor for PostNL packages."""
from datetime import timedelta
import logging
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['postnl_api==1.0.2']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Information provided by PostNL'
diff --git a/homeassistant/components/prezzibenzina/manifest.json b/homeassistant/components/prezzibenzina/manifest.json
new file mode 100644
index 00000000000..2427ebbfdb0
--- /dev/null
+++ b/homeassistant/components/prezzibenzina/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "prezzibenzina",
+ "name": "Prezzibenzina",
+ "documentation": "https://www.home-assistant.io/components/prezzibenzina",
+ "requirements": [
+ "prezzibenzina-py==1.1.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/prezzibenzina/sensor.py b/homeassistant/components/prezzibenzina/sensor.py
index 171fea53314..9814e9463df 100644
--- a/homeassistant/components/prezzibenzina/sensor.py
+++ b/homeassistant/components/prezzibenzina/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the PrezziBenzina.it service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.prezzibenzina/
-"""
+"""Support for the PrezziBenzina.it service."""
import datetime as dt
from datetime import timedelta
import logging
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['prezzibenzina-py==1.1.4']
-
_LOGGER = logging.getLogger(__name__)
ATTR_FUEL = 'fuel'
diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py
index c88ece033df..a6b4b3fd0f1 100644
--- a/homeassistant/components/proliphix/climate.py
+++ b/homeassistant/components/proliphix/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for Proliphix NT10e Thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.proliphix/
-"""
+"""Support for Proliphix NT10e Thermostats."""
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
@@ -14,8 +9,6 @@ from homeassistant.const import (
ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['proliphix==0.4.1']
-
ATTR_FAN = 'fan'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/proliphix/manifest.json b/homeassistant/components/proliphix/manifest.json
new file mode 100644
index 00000000000..3aa356823c1
--- /dev/null
+++ b/homeassistant/components/proliphix/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "proliphix",
+ "name": "Proliphix",
+ "documentation": "https://www.home-assistant.io/components/proliphix",
+ "requirements": [
+ "proliphix==0.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py
index de0de8ae162..5119a5e0fdf 100644
--- a/homeassistant/components/prometheus/__init__.py
+++ b/homeassistant/components/prometheus/__init__.py
@@ -14,15 +14,11 @@ from homeassistant.helpers import entityfilter, state as state_helper
import homeassistant.helpers.config_validation as cv
from homeassistant.util.temperature import fahrenheit_to_celsius
-REQUIREMENTS = ['prometheus_client==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
API_ENDPOINT = '/api/prometheus'
DOMAIN = 'prometheus'
-DEPENDENCIES = ['http']
-
CONF_FILTER = 'filter'
CONF_PROM_NAMESPACE = 'namespace'
diff --git a/homeassistant/components/prometheus/manifest.json b/homeassistant/components/prometheus/manifest.json
new file mode 100644
index 00000000000..d9699be6bf7
--- /dev/null
+++ b/homeassistant/components/prometheus/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "prometheus",
+ "name": "Prometheus",
+ "documentation": "https://www.home-assistant.io/components/prometheus",
+ "requirements": [
+ "prometheus_client==0.2.0"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/prowl/manifest.json b/homeassistant/components/prowl/manifest.json
new file mode 100644
index 00000000000..a8b4893c995
--- /dev/null
+++ b/homeassistant/components/prowl/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "prowl",
+ "name": "Prowl",
+ "documentation": "https://www.home-assistant.io/components/prowl",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/prowl/notify.py b/homeassistant/components/prowl/notify.py
index 6d911789121..1f2067cc660 100644
--- a/homeassistant/components/prowl/notify.py
+++ b/homeassistant/components/prowl/notify.py
@@ -1,9 +1,4 @@
-"""
-Prowl notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.prowl/
-"""
+"""Prowl notification service."""
import asyncio
import logging
diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py
index 0a617bcec90..c696c36f94c 100644
--- a/homeassistant/components/proximity/__init__.py
+++ b/homeassistant/components/proximity/__init__.py
@@ -25,7 +25,6 @@ DEFAULT_DIST_TO_ZONE = 'not set'
DEFAULT_NEAREST = 'not set'
DEFAULT_PROXIMITY_ZONE = 'home'
DEFAULT_TOLERANCE = 1
-DEPENDENCIES = ['zone', 'device_tracker']
DOMAIN = 'proximity'
UNITS = ['km', 'm', 'mi', 'ft']
diff --git a/homeassistant/components/proximity/manifest.json b/homeassistant/components/proximity/manifest.json
new file mode 100644
index 00000000000..335bea82fc9
--- /dev/null
+++ b/homeassistant/components/proximity/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "proximity",
+ "name": "Proximity",
+ "documentation": "https://www.home-assistant.io/components/proximity",
+ "requirements": [],
+ "dependencies": [
+ "device_tracker",
+ "zone"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py
index 3e6e4911d27..7c535e65bc8 100644
--- a/homeassistant/components/proxy/camera.py
+++ b/homeassistant/components/proxy/camera.py
@@ -1,9 +1,4 @@
-"""
-Proxy camera platform that enables image processing of camera data.
-
-For more details about this platform, please refer to the documentation
-https://www.home-assistant.io/components/camera.proxy/
-"""
+"""Proxy camera platform that enables image processing of camera data."""
import asyncio
import logging
@@ -17,8 +12,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pillow==5.4.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_CACHE_IMAGES = 'cache_images'
diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json
new file mode 100644
index 00000000000..a4a33efa2cd
--- /dev/null
+++ b/homeassistant/components/proxy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "proxy",
+ "name": "Proxy",
+ "documentation": "https://www.home-assistant.io/components/proxy",
+ "requirements": [
+ "pillow==5.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ps4/.translations/da.json b/homeassistant/components/ps4/.translations/da.json
index 7c5f9e7621c..801317a9e7f 100644
--- a/homeassistant/components/ps4/.translations/da.json
+++ b/homeassistant/components/ps4/.translations/da.json
@@ -25,6 +25,9 @@
},
"description": "Indtast dine PlayStation 4 oplysninger. For 'PIN' skal du navigere til 'Indstillinger' p\u00e5 din PlayStation 4 konsol. G\u00e5 derefter til 'Indstillinger for mobilapp-forbindelse' og v\u00e6lg 'Tilf\u00f8j enhed'. Indtast den PIN der vises.",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/es.json b/homeassistant/components/ps4/.translations/es.json
index 65798ba4d0c..a159cd0552a 100644
--- a/homeassistant/components/ps4/.translations/es.json
+++ b/homeassistant/components/ps4/.translations/es.json
@@ -9,10 +9,12 @@
},
"error": {
"login_failed": "No se ha podido emparejar con PlayStation 4. Verifique que el PIN sea correcto.",
+ "no_ipaddress": "Introduce la direcci\u00f3n IP de la PlayStation 4 que quieres configurar.",
"not_ready": "PlayStation 4 no est\u00e1 encendido o conectado a la red."
},
"step": {
"creds": {
+ "description": "Credenciales necesarias. Pulsa 'Enviar' y, a continuaci\u00f3n, en la app de segunda pantalla de PS4, actualiza la lista de dispositivos y selecciona el dispositivo 'Home-Assistant' para continuar.",
"title": "PlayStation 4"
},
"link": {
@@ -22,6 +24,15 @@
"name": "Nombre",
"region": "Regi\u00f3n"
},
+ "description": "Introduce la informaci\u00f3n de tu PlayStation 4. Para el 'PIN', ve a los 'Ajustes' en tu PlayStation 4. Despu\u00e9s dir\u00edgete hasta 'Ajustes de conexi\u00f3n de la aplicaci\u00f3n para m\u00f3viles' y selecciona 'A\u00f1adir dispositivo'. Introduce el PIN mostrado. Consulta la [documentaci\u00f3n](https://www.home-assistant.io/components/ps4/) para m\u00e1s informaci\u00f3n.",
+ "title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "ip_address": "Direcci\u00f3n IP (d\u00e9jalo en blanco si usas la detecci\u00f3n autom\u00e1tica).",
+ "mode": "Modo configuraci\u00f3n"
+ },
+ "description": "Selecciona el modo de configuraci\u00f3n. El campo de direcci\u00f3n IP puede dejarse en blanco si se selecciona la detecci\u00f3n autom\u00e1tica, ya que los dispositivos se detectar\u00e1n autom\u00e1ticamente.",
"title": "PlayStation 4"
}
},
diff --git a/homeassistant/components/ps4/.translations/fr.json b/homeassistant/components/ps4/.translations/fr.json
index bb654eed228..cfd65c910d9 100644
--- a/homeassistant/components/ps4/.translations/fr.json
+++ b/homeassistant/components/ps4/.translations/fr.json
@@ -9,6 +9,7 @@
},
"error": {
"login_failed": "\u00c9chec de l'association \u00e0 la PlayStation 4. V\u00e9rifiez que le code PIN est correct.",
+ "no_ipaddress": "Entrez l'adresse IP de la PlayStation 4 que vous souhaitez configurer.",
"not_ready": "PlayStation 4 n'est pas allum\u00e9e ou connect\u00e9e au r\u00e9seau."
},
"step": {
@@ -25,6 +26,14 @@
},
"description": "Entrez vos informations PlayStation 4. Pour \"Code PIN\", acc\u00e9dez \u00e0 \"Param\u00e8tres\" sur votre console PlayStation 4. Ensuite, acc\u00e9dez \u00e0 \"Param\u00e8tres de connexion de l'application mobile\" et s\u00e9lectionnez \"Ajouter un p\u00e9riph\u00e9rique\". Entrez le code PIN qui est affich\u00e9.",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "ip_address": "Adresse IP (laissez vide si vous utilisez la d\u00e9couverte automatique).",
+ "mode": "Mode de configuration"
+ },
+ "description": "S\u00e9lectionnez le mode de configuration. Le champ Adresse IP peut rester vide si vous s\u00e9lectionnez D\u00e9couverte automatique, car les p\u00e9riph\u00e9riques seront automatiquement d\u00e9couverts.",
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/it.json b/homeassistant/components/ps4/.translations/it.json
index 5e83d7bd39c..635fbd7b479 100644
--- a/homeassistant/components/ps4/.translations/it.json
+++ b/homeassistant/components/ps4/.translations/it.json
@@ -9,6 +9,7 @@
},
"error": {
"login_failed": "Accoppiamento alla PlayStation 4 fallito. Verifica che il PIN sia corretto.",
+ "no_ipaddress": "Inserisci l'indirizzo IP della PlayStation 4 che desideri configurare.",
"not_ready": "La PlayStation 4 non \u00e8 accesa o non \u00e8 collegata alla rete."
},
"step": {
@@ -25,6 +26,12 @@
},
"description": "Inserisci le informazioni della tua PlayStation 4. Per il \"PIN\", vai su \"Impostazioni\" sulla tua console PlayStation 4. Quindi accedi a \"Impostazioni connessione app mobile\" e seleziona \"Aggiungi dispositivo\". Inserisci il PIN che viene visualizzato.",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "mode": "Modalit\u00e0 di configurazione"
+ },
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/ko.json b/homeassistant/components/ps4/.translations/ko.json
index ba864f07320..d42586505d9 100644
--- a/homeassistant/components/ps4/.translations/ko.json
+++ b/homeassistant/components/ps4/.translations/ko.json
@@ -32,7 +32,7 @@
"ip_address": "IP \uc8fc\uc18c (\uc790\ub3d9 \uac80\uc0c9\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\uc6cc\ub450\uc138\uc694)",
"mode": "\uad6c\uc131 \ubaa8\ub4dc"
},
- "description": "\uad6c\uc131 \ubaa8\ub4dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc790\ub3d9 \uac80\uc0c9\uc744 \uc120\ud0dd\ud558\uba74 \uae30\uae30\uac00 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub418\ubbc0\ub85c IP \uc8fc\uc18c \ud544\ub4dc\ub294 \ube44\uc6cc\ub458 \uc218 \uc788\uc2b5\ub2c8\ub2e4.",
+ "description": "\uad6c\uc131 \ubaa8\ub4dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc790\ub3d9 \uac80\uc0c9\uc744 \uc120\ud0dd\ud558\uba74 \uae30\uae30\uac00 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub418\ubbc0\ub85c IP \uc8fc\uc18c \ud544\ub4dc\ub294 \ube44\uc6cc\ub450\uc154\ub3c4 \ub429\ub2c8\ub2e4.",
"title": "PlayStation 4"
}
},
diff --git a/homeassistant/components/ps4/.translations/nn.json b/homeassistant/components/ps4/.translations/nn.json
new file mode 100644
index 00000000000..b3302389c88
--- /dev/null
+++ b/homeassistant/components/ps4/.translations/nn.json
@@ -0,0 +1,13 @@
+{
+ "config": {
+ "abort": {
+ "port_987_bind_error": "Kunne ikkje binda til port 987. Sj\u00e5 [dokumentasjonen](https://www.home-assistant.io/components/ps4/) for meir informasjon.",
+ "port_997_bind_error": "Kunne ikkje binde til port 997. Sj\u00e5 [dokumentasjonen] (https://www.home-assistant.io/components/ps4/) for ytterlegare informasjon."
+ },
+ "step": {
+ "mode": {
+ "title": "Playstation 4"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ps4/.translations/no.json b/homeassistant/components/ps4/.translations/no.json
index 32687882da2..ea2c0b37f6e 100644
--- a/homeassistant/components/ps4/.translations/no.json
+++ b/homeassistant/components/ps4/.translations/no.json
@@ -4,16 +4,17 @@
"credential_error": "Feil ved henting av legitimasjon.",
"devices_configured": "Alle enheter som ble funnet er allerede konfigurert.",
"no_devices_found": "Ingen PlayStation 4 enheter funnet p\u00e5 nettverket.",
- "port_987_bind_error": "Kunne ikke binde til port 987.",
- "port_997_bind_error": "Kunne ikke binde til port 997."
+ "port_987_bind_error": "Kunne ikke binde til port 987. Se [dokumentasjonen](https://www.home-assistant.io/components/ps4/) for mer info.",
+ "port_997_bind_error": "Kunne ikke binde til port 997. Se [dokumentasjonen] (https://www.home-assistant.io/components/ps4/) for videre informasjon."
},
"error": {
"login_failed": "Klarte ikke \u00e5 koble til PlayStation 4. Bekreft at PIN koden er riktig.",
+ "no_ipaddress": "Angi IP adressen til din PlayStation 4 som du \u00f8nsker konfigurere.",
"not_ready": "PlayStation 4 er ikke p\u00e5sl\u00e5tt eller koblet til nettverk."
},
"step": {
"creds": {
- "description": "Legitimasjon n\u00f8dvendig. Trykk \"Send\" og deretter i PS4-ens andre skjerm app, kan du oppdatere enheter, og velg \"Home-Assistent' enheten for \u00e5 fortsette.",
+ "description": "Legitimasjon n\u00f8dvendig. Trykk \"Send\" og deretter i PS4-ens andre skjerm app, kan du oppdatere enheter, og velg \"Home-Assistant' enheten for \u00e5 fortsette.",
"title": "PlayStation 4"
},
"link": {
@@ -25,6 +26,14 @@
},
"description": "Skriv inn PlayStation 4 informasjonen din. For 'PIN', naviger til 'Innstillinger' p\u00e5 PlayStation 4 konsollen, deretter navigerer du til 'Innstillinger for mobilapp forbindelse' og velger 'Legg til enhet'. Skriv inn PIN-koden som vises.",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "ip_address": "IP- adresse (Ikke fyll ut hvis du bruker Auto Discovery).",
+ "mode": "Konfigureringsmodus"
+ },
+ "description": "Velg modus for konfigurasjon. Feltet IP-adresse kan st\u00e5 tomt dersom du velger Auto Discovery, da enheter vil bli oppdaget automatisk.",
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/pl.json b/homeassistant/components/ps4/.translations/pl.json
index eea4eda0810..d38dabe3188 100644
--- a/homeassistant/components/ps4/.translations/pl.json
+++ b/homeassistant/components/ps4/.translations/pl.json
@@ -9,6 +9,7 @@
},
"error": {
"login_failed": "Nie uda\u0142o si\u0119 sparowa\u0107 z PlayStation 4. Sprawd\u017a, czy PIN jest poprawny.",
+ "no_ipaddress": "Wprowad\u017a adres IP PlayStation 4, kt\u00f3ry chcesz skonfigurowa\u0107.",
"not_ready": "PlayStation 4 nie jest w\u0142\u0105czona lub po\u0142\u0105czona z sieci\u0105."
},
"step": {
@@ -25,6 +26,14 @@
},
"description": "Wprowad\u017a informacje o PlayStation 4. Aby uzyska\u0107 'PIN', przejd\u017a do 'Ustawienia' na konsoli PlayStation 4. Nast\u0119pnie przejd\u017a do 'Ustawienia po\u0142\u0105czenia aplikacji mobilnej' i wybierz 'Dodaj urz\u0105dzenie'. Wprowad\u017a wy\u015bwietlony kod PIN.",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "ip_address": "Adres IP (pozostaw puste, je\u015bli u\u017cywasz funkcji Auto Discovery).",
+ "mode": "Tryb konfiguracji"
+ },
+ "description": "Wybierz tryb konfiguracji. Pole adresu IP mo\u017cna pozostawi\u0107 puste, je\u015bli wybierzesz opcj\u0119 Auto Discovery, poniewa\u017c urz\u0105dzenia zostan\u0105 automatycznie wykryte.",
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/pt.json b/homeassistant/components/ps4/.translations/pt.json
index 34a5ebfc4db..5d4c8e12283 100644
--- a/homeassistant/components/ps4/.translations/pt.json
+++ b/homeassistant/components/ps4/.translations/pt.json
@@ -7,10 +7,14 @@
"link": {
"data": {
"code": "PIN",
+ "ip_address": "Endere\u00e7o de IP",
"name": "Nome",
"region": "Regi\u00e3o"
},
"title": "PlayStation 4"
+ },
+ "mode": {
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/ru.json b/homeassistant/components/ps4/.translations/ru.json
index 424d0964729..d69213d7d75 100644
--- a/homeassistant/components/ps4/.translations/ru.json
+++ b/homeassistant/components/ps4/.translations/ru.json
@@ -3,18 +3,18 @@
"abort": {
"credential_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445.",
"devices_configured": "\u0412\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b.",
- "no_devices_found": "\u0412 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 PlayStation 4.",
+ "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 PlayStation 4 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.",
"port_987_bind_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u043e\u0440\u0442\u0443 987. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/).",
"port_997_bind_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u043e\u0440\u0442\u0443 997. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/)."
},
"error": {
- "login_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 PlayStation 4. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e PIN-\u043a\u043e\u0434 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439.",
+ "login_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 PlayStation 4. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e PIN-\u043a\u043e\u0434 \u0432\u0432\u0435\u0434\u0435\u043d \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e.",
"no_ipaddress": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 PlayStation 4.",
"not_ready": "PlayStation 4 \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u0438\u043b\u0438 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043a \u0441\u0435\u0442\u0438."
},
"step": {
"creds": {
- "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 'PS4 Second Screen' \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e 'Home-Assistant', \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c.",
+ "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 'PS4 Second Screen' \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e 'Home-Assistant'.",
"title": "PlayStation 4"
},
"link": {
diff --git a/homeassistant/components/ps4/.translations/sv.json b/homeassistant/components/ps4/.translations/sv.json
index d35efbd4b00..642497b1074 100644
--- a/homeassistant/components/ps4/.translations/sv.json
+++ b/homeassistant/components/ps4/.translations/sv.json
@@ -25,6 +25,12 @@
},
"description": "Ange din PlayStation 4 information. F\u00f6r 'PIN', navigera till 'Inst\u00e4llningar' p\u00e5 din PlayStation 4 konsol. Navigera sedan till \"Inst\u00e4llningar f\u00f6r mobilappanslutning\" och v\u00e4lj \"L\u00e4gg till enhet\". Ange PIN-koden som visas.",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "mode": "Konfigureringsl\u00e4ge"
+ },
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/th.json b/homeassistant/components/ps4/.translations/th.json
index a48089bfdd6..b33002bcda8 100644
--- a/homeassistant/components/ps4/.translations/th.json
+++ b/homeassistant/components/ps4/.translations/th.json
@@ -12,6 +12,9 @@
"region": "\u0e20\u0e39\u0e21\u0e34\u0e20\u0e32\u0e04"
},
"title": "PlayStation 4"
+ },
+ "mode": {
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/.translations/zh-Hant.json b/homeassistant/components/ps4/.translations/zh-Hant.json
index b4f45986c1e..54740e2c727 100644
--- a/homeassistant/components/ps4/.translations/zh-Hant.json
+++ b/homeassistant/components/ps4/.translations/zh-Hant.json
@@ -9,6 +9,7 @@
},
"error": {
"login_failed": "PlayStation 4 \u914d\u5c0d\u5931\u6557\uff0c\u8acb\u78ba\u8a8d PIN \u78bc\u3002",
+ "no_ipaddress": "\u8f38\u5165\u6240\u8981\u8a2d\u5b9a\u7684 PlayStation 4 \u4e4b IP \u4f4d\u5740\u3002",
"not_ready": "PlayStation 4 \u4e26\u672a\u958b\u555f\u6216\u672a\u9023\u7dda\u81f3\u7db2\u8def\u3002"
},
"step": {
@@ -25,6 +26,14 @@
},
"description": "\u8f38\u5165\u60a8\u7684 PlayStation 4 \u8cc7\u8a0a\uff0c\u300cPIN\u300d\u65bc PlayStation 4 \u4e3b\u6a5f\u7684\u300c\u8a2d\u5b9a\u300d\u5167\uff0c\u4e26\u65bc\u300c\u884c\u52d5\u7a0b\u5f0f\u9023\u7dda\u8a2d\u5b9a\uff08Mobile App Connection Settings\uff09\u300d\u4e2d\u9078\u64c7\u300c\u65b0\u589e\u88dd\u7f6e\u300d\u3002\u8f38\u5165\u6240\u986f\u793a\u7684 PIN \u78bc\u3002",
"title": "PlayStation 4"
+ },
+ "mode": {
+ "data": {
+ "ip_address": "IP \u4f4d\u5740\uff08\u5982\u679c\u4f7f\u7528\u81ea\u52d5\u63a2\u7d22\u65b9\u5f0f\uff0c\u8acb\u4fdd\u7559\u7a7a\u767d\uff09\u3002",
+ "mode": "\u8a2d\u5b9a\u6a21\u5f0f"
+ },
+ "description": "\u9078\u64c7\u6a21\u5f0f\u4ee5\u9032\u884c\u8a2d\u5b9a\u3002\u5047\u5982\u9078\u64c7\u81ea\u52d5\u63a2\u7d22\u6a21\u5f0f\u7684\u8a71\uff0c\u7531\u65bc\u6703\u81ea\u52d5\u9032\u884c\u88dd\u7f6e\u641c\u5c0b\uff0cIP \u4f4d\u5740\u53ef\u4fdd\u7559\u70ba\u7a7a\u767d\u3002",
+ "title": "PlayStation 4"
}
},
"title": "PlayStation 4"
diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py
index 9183bbe1989..22c21fcffbe 100644
--- a/homeassistant/components/ps4/__init__.py
+++ b/homeassistant/components/ps4/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for PlayStation 4 consoles.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/ps4/
-"""
+"""Support for PlayStation 4 consoles."""
import logging
from homeassistant.const import CONF_REGION
@@ -14,8 +9,6 @@ from .const import DOMAIN # noqa: pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyps4-homeassistant==0.5.2']
-
async def async_setup(hass, config):
"""Set up the PS4 Component."""
diff --git a/homeassistant/components/ps4/manifest.json b/homeassistant/components/ps4/manifest.json
new file mode 100644
index 00000000000..605dd3f530c
--- /dev/null
+++ b/homeassistant/components/ps4/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ps4",
+ "name": "Ps4",
+ "documentation": "https://www.home-assistant.io/components/ps4",
+ "requirements": [
+ "pyps4-homeassistant==0.5.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py
index 80c1fda52de..3382cd6fe43 100644
--- a/homeassistant/components/ps4/media_player.py
+++ b/homeassistant/components/ps4/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for PlayStation 4 consoles.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/ps4/
-"""
+"""Support for PlayStation 4 consoles."""
import logging
import socket
@@ -22,8 +17,6 @@ from homeassistant.util.json import load_json, save_json
from .const import DOMAIN as PS4_DOMAIN, REGIONS as deprecated_regions
-DEPENDENCIES = ['ps4']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_PS4 = SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \
diff --git a/homeassistant/components/pulseaudio_loopback/manifest.json b/homeassistant/components/pulseaudio_loopback/manifest.json
new file mode 100644
index 00000000000..58a2871e027
--- /dev/null
+++ b/homeassistant/components/pulseaudio_loopback/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "pulseaudio_loopback",
+ "name": "Pulseaudio loopback",
+ "documentation": "https://www.home-assistant.io/components/pulseaudio_loopback",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py
index f112608d760..9ec6587f678 100644
--- a/homeassistant/components/pulseaudio_loopback/switch.py
+++ b/homeassistant/components/pulseaudio_loopback/switch.py
@@ -1,9 +1,4 @@
-"""
-Switch logic for loading/unloading pulseaudio loopback modules.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.pulseaudio_loopback/
-"""
+"""Switch logic for loading/unloading pulseaudio loopback modules."""
import logging
import re
import socket
diff --git a/homeassistant/components/push/camera.py b/homeassistant/components/push/camera.py
index 5490cd1508c..c962aee91ca 100644
--- a/homeassistant/components/push/camera.py
+++ b/homeassistant/components/push/camera.py
@@ -1,9 +1,4 @@
-"""
-Camera platform that receives images through HTTP POST.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/camera.push/
-"""
+"""Camera platform that receives images through HTTP POST."""
import logging
import asyncio
@@ -22,8 +17,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_point_in_utc_time
import homeassistant.util.dt as dt_util
-DEPENDENCIES = ['webhook']
-
_LOGGER = logging.getLogger(__name__)
CONF_BUFFER_SIZE = 'buffer'
diff --git a/homeassistant/components/push/manifest.json b/homeassistant/components/push/manifest.json
new file mode 100644
index 00000000000..278638caff8
--- /dev/null
+++ b/homeassistant/components/push/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "push",
+ "name": "Push",
+ "documentation": "https://www.home-assistant.io/components/push",
+ "requirements": [],
+ "dependencies": ["webhook"],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/pushbullet/manifest.json b/homeassistant/components/pushbullet/manifest.json
new file mode 100644
index 00000000000..51e77959d7a
--- /dev/null
+++ b/homeassistant/components/pushbullet/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pushbullet",
+ "name": "Pushbullet",
+ "documentation": "https://www.home-assistant.io/components/pushbullet",
+ "requirements": [
+ "pushbullet.py==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pushbullet/notify.py b/homeassistant/components/pushbullet/notify.py
index f0b4ec24da8..d1d9a6449ef 100644
--- a/homeassistant/components/pushbullet/notify.py
+++ b/homeassistant/components/pushbullet/notify.py
@@ -1,9 +1,4 @@
-"""
-Pushbullet platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.pushbullet/
-"""
+"""Pushbullet platform for notify component."""
import logging
import mimetypes
@@ -16,8 +11,6 @@ from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['pushbullet.py==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_URL = 'url'
diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py
index 9b26bdfbbe9..50fa407620a 100644
--- a/homeassistant/components/pushbullet/sensor.py
+++ b/homeassistant/components/pushbullet/sensor.py
@@ -1,9 +1,4 @@
-"""
-Pushbullet platform for sensor component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.pushbullet/
-"""
+"""Pushbullet platform for sensor component."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pushbullet.py==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
diff --git a/homeassistant/components/pushetta/manifest.json b/homeassistant/components/pushetta/manifest.json
new file mode 100644
index 00000000000..b42180c7268
--- /dev/null
+++ b/homeassistant/components/pushetta/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pushetta",
+ "name": "Pushetta",
+ "documentation": "https://www.home-assistant.io/components/pushetta",
+ "requirements": [
+ "pushetta==1.0.15"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pushetta/notify.py b/homeassistant/components/pushetta/notify.py
index 106c0641a69..5c776523d12 100644
--- a/homeassistant/components/pushetta/notify.py
+++ b/homeassistant/components/pushetta/notify.py
@@ -1,9 +1,4 @@
-"""
-Pushetta platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.pushetta/
-"""
+"""Pushetta platform for notify component."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService)
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pushetta==1.0.15']
-
CONF_CHANNEL_NAME = 'channel_name'
CONF_SEND_TEST_MSG = 'send_test_msg'
diff --git a/homeassistant/components/pushover/manifest.json b/homeassistant/components/pushover/manifest.json
new file mode 100644
index 00000000000..30dd35720de
--- /dev/null
+++ b/homeassistant/components/pushover/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pushover",
+ "name": "Pushover",
+ "documentation": "https://www.home-assistant.io/components/pushover",
+ "requirements": [
+ "python-pushover==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pushover/notify.py b/homeassistant/components/pushover/notify.py
index 78e9ed11c95..d9be3428d59 100644
--- a/homeassistant/components/pushover/notify.py
+++ b/homeassistant/components/pushover/notify.py
@@ -1,9 +1,4 @@
-"""
-Pushover platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.pushover/
-"""
+"""Pushover platform for notify component."""
import logging
import voluptuous as vol
@@ -15,7 +10,6 @@ from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['python-pushover==0.3']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/pushsafer/manifest.json b/homeassistant/components/pushsafer/manifest.json
new file mode 100644
index 00000000000..300d0ead4a5
--- /dev/null
+++ b/homeassistant/components/pushsafer/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "pushsafer",
+ "name": "Pushsafer",
+ "documentation": "https://www.home-assistant.io/components/pushsafer",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py
index a1fa2b7409c..c64b861631a 100644
--- a/homeassistant/components/pushsafer/notify.py
+++ b/homeassistant/components/pushsafer/notify.py
@@ -1,9 +1,4 @@
-"""
-Pushsafer platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.pushsafer/
-"""
+"""Pushsafer platform for notify component."""
import base64
import logging
import mimetypes
diff --git a/homeassistant/components/pvoutput/manifest.json b/homeassistant/components/pvoutput/manifest.json
new file mode 100644
index 00000000000..b61c7100828
--- /dev/null
+++ b/homeassistant/components/pvoutput/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "pvoutput",
+ "name": "Pvoutput",
+ "documentation": "https://www.home-assistant.io/components/pvoutput",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py
index dbcd38af3cc..22368212442 100644
--- a/homeassistant/components/pvoutput/sensor.py
+++ b/homeassistant/components/pvoutput/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for getting collected information from PVOutput.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.pvoutput/
-"""
+"""Support for getting collected information from PVOutput."""
import logging
from collections import namedtuple
from datetime import timedelta
diff --git a/homeassistant/components/pyload/manifest.json b/homeassistant/components/pyload/manifest.json
new file mode 100644
index 00000000000..437bd3bc4d2
--- /dev/null
+++ b/homeassistant/components/pyload/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "pyload",
+ "name": "Pyload",
+ "documentation": "https://www.home-assistant.io/components/pyload",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py
index 78a191c16f4..7c7d1e7ae08 100644
--- a/homeassistant/components/pyload/sensor.py
+++ b/homeassistant/components/pyload/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring pyLoad.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.pyload/
-"""
+"""Support for monitoring pyLoad."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py
index d639b638033..a6c7a87ae38 100644
--- a/homeassistant/components/python_script/__init__.py
+++ b/homeassistant/components/python_script/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to allow running Python scripts.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/python_script/
-"""
+"""Component to allow running Python scripts."""
import datetime
import glob
import logging
@@ -18,8 +13,6 @@ from homeassistant.loader import bind_hass
from homeassistant.util import sanitize_filename
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['restrictedpython==4.0b8']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'python_script'
diff --git a/homeassistant/components/python_script/manifest.json b/homeassistant/components/python_script/manifest.json
new file mode 100644
index 00000000000..0f88513bb45
--- /dev/null
+++ b/homeassistant/components/python_script/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "python_script",
+ "name": "Python script",
+ "documentation": "https://www.home-assistant.io/components/python_script",
+ "requirements": [
+ "restrictedpython==4.0b8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/python_script/services.yaml b/homeassistant/components/python_script/services.yaml
new file mode 100644
index 00000000000..835f6402481
--- /dev/null
+++ b/homeassistant/components/python_script/services.yaml
@@ -0,0 +1,4 @@
+# Describes the format for available python_script services
+
+reload:
+ description: Reload all available python_scripts
diff --git a/homeassistant/components/qbittorrent/manifest.json b/homeassistant/components/qbittorrent/manifest.json
new file mode 100644
index 00000000000..5fb850739d8
--- /dev/null
+++ b/homeassistant/components/qbittorrent/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "qbittorrent",
+ "name": "Qbittorrent",
+ "documentation": "https://www.home-assistant.io/components/qbittorrent",
+ "requirements": [
+ "python-qbittorrent==0.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/qbittorrent/sensor.py b/homeassistant/components/qbittorrent/sensor.py
index 8718f3a9d74..eb2529f0221 100644
--- a/homeassistant/components/qbittorrent/sensor.py
+++ b/homeassistant/components/qbittorrent/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring the qBittorrent API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.qbittorrent/
-"""
+"""Support for monitoring the qBittorrent API."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
-REQUIREMENTS = ['python-qbittorrent==0.3.1']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPE_CURRENT_STATUS = 'current_status'
diff --git a/homeassistant/components/qnap/manifest.json b/homeassistant/components/qnap/manifest.json
new file mode 100644
index 00000000000..f02d416c7e6
--- /dev/null
+++ b/homeassistant/components/qnap/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "qnap",
+ "name": "Qnap",
+ "documentation": "https://www.home-assistant.io/components/qnap",
+ "requirements": [
+ "qnapstats==0.2.7"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@colinodell"
+ ]
+}
diff --git a/homeassistant/components/qnap/sensor.py b/homeassistant/components/qnap/sensor.py
index a6a9c6e30d0..34eb850e4b1 100644
--- a/homeassistant/components/qnap/sensor.py
+++ b/homeassistant/components/qnap/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for QNAP NAS Sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.qnap/
-"""
+"""Support for QNAP NAS Sensors."""
import logging
from datetime import timedelta
@@ -18,8 +13,6 @@ from homeassistant.util import Throttle
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['qnapstats==0.2.7']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DRIVE = 'Drive'
diff --git a/homeassistant/components/qrcode/image_processing.py b/homeassistant/components/qrcode/image_processing.py
index 00f4ad025b2..e5836135512 100644
--- a/homeassistant/components/qrcode/image_processing.py
+++ b/homeassistant/components/qrcode/image_processing.py
@@ -1,15 +1,8 @@
-"""
-Support for the QR image processing.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.qr/
-"""
+"""Support for the QR image processing."""
from homeassistant.core import split_entity_id
from homeassistant.components.image_processing import (
ImageProcessingEntity, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME)
-REQUIREMENTS = ['pyzbar==0.1.7', 'pillow==5.4.1']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the demo image processing platform."""
diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json
new file mode 100644
index 00000000000..96a351ac453
--- /dev/null
+++ b/homeassistant/components/qrcode/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "qrcode",
+ "name": "Qrcode",
+ "documentation": "https://www.home-assistant.io/components/qrcode",
+ "requirements": [
+ "pillow==5.4.1",
+ "pyzbar==0.1.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/quantum_gateway/device_tracker.py b/homeassistant/components/quantum_gateway/device_tracker.py
index 90ba3575cfa..e91fe99b7cd 100644
--- a/homeassistant/components/quantum_gateway/device_tracker.py
+++ b/homeassistant/components/quantum_gateway/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Verizon FiOS Quantum Gateways.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.quantum_gateway/
-"""
+"""Support for Verizon FiOS Quantum Gateways."""
import logging
from requests.exceptions import RequestException
@@ -14,8 +9,6 @@ from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA,
from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_SSL)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['quantum-gateway==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = 'myfiosgateway.com'
diff --git a/homeassistant/components/quantum_gateway/manifest.json b/homeassistant/components/quantum_gateway/manifest.json
new file mode 100644
index 00000000000..9c062482a4c
--- /dev/null
+++ b/homeassistant/components/quantum_gateway/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "quantum_gateway",
+ "name": "Quantum gateway",
+ "documentation": "https://www.home-assistant.io/components/quantum_gateway",
+ "requirements": [
+ "quantum-gateway==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@cisasteelersfan"
+ ]
+}
diff --git a/homeassistant/components/qwikswitch/__init__.py b/homeassistant/components/qwikswitch/__init__.py
index 63e30a9491e..3940f055ff8 100644
--- a/homeassistant/components/qwikswitch/__init__.py
+++ b/homeassistant/components/qwikswitch/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for Qwikswitch devices.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/qwikswitch/
-"""
+"""Support for Qwikswitch devices."""
import logging
import voluptuous as vol
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyqwikswitch==0.8']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'qwikswitch'
@@ -119,7 +112,8 @@ class QSToggleEntity(QSEntity):
async def async_setup(hass, config):
"""Qwiskswitch component setup."""
from pyqwikswitch.async_ import QSUsb
- from pyqwikswitch import CMD_BUTTONS, QS_CMD, QS_ID, QSType, SENSORS
+ from pyqwikswitch.qwikswitch import (
+ CMD_BUTTONS, QS_CMD, QS_ID, QSType, SENSORS)
# Add cmd's to in /&listen packets will fire events
# By default only buttons of type [TOGGLE,SCENE EXE,LEVEL]
diff --git a/homeassistant/components/qwikswitch/binary_sensor.py b/homeassistant/components/qwikswitch/binary_sensor.py
index 17021f7a9e9..8042035b9c1 100644
--- a/homeassistant/components/qwikswitch/binary_sensor.py
+++ b/homeassistant/components/qwikswitch/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Qwikswitch Binary Sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.qwikswitch/
-"""
+"""Support for Qwikswitch Binary Sensors."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -11,8 +6,6 @@ from homeassistant.core import callback
from . import DOMAIN as QWIKSWITCH, QSEntity
-DEPENDENCIES = [QWIKSWITCH]
-
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +28,7 @@ class QSBinarySensor(QSEntity, BinarySensorDevice):
def __init__(self, sensor):
"""Initialize the sensor."""
- from pyqwikswitch import SENSORS
+ from pyqwikswitch.qwikswitch import SENSORS
super().__init__(sensor['id'], sensor['name'])
self.channel = sensor['channel']
diff --git a/homeassistant/components/qwikswitch/light.py b/homeassistant/components/qwikswitch/light.py
index 46a0a88483b..1adcef56ffa 100644
--- a/homeassistant/components/qwikswitch/light.py
+++ b/homeassistant/components/qwikswitch/light.py
@@ -1,15 +1,8 @@
-"""
-Support for Qwikswitch Relays and Dimmers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.qwikswitch/
-"""
+"""Support for Qwikswitch Relays and Dimmers."""
from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light
from . import DOMAIN as QWIKSWITCH, QSToggleEntity
-DEPENDENCIES = [QWIKSWITCH]
-
async def async_setup_platform(hass, _, add_entities, discovery_info=None):
"""Add lights from the main Qwikswitch component."""
diff --git a/homeassistant/components/qwikswitch/manifest.json b/homeassistant/components/qwikswitch/manifest.json
new file mode 100644
index 00000000000..4907cb462b6
--- /dev/null
+++ b/homeassistant/components/qwikswitch/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "qwikswitch",
+ "name": "Qwikswitch",
+ "documentation": "https://www.home-assistant.io/components/qwikswitch",
+ "requirements": [
+ "pyqwikswitch==0.93"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@kellerza"
+ ]
+}
diff --git a/homeassistant/components/qwikswitch/sensor.py b/homeassistant/components/qwikswitch/sensor.py
index 07d0247e4f6..047ec3475a5 100644
--- a/homeassistant/components/qwikswitch/sensor.py
+++ b/homeassistant/components/qwikswitch/sensor.py
@@ -1,17 +1,10 @@
-"""
-Support for Qwikswitch Sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.qwikswitch/
-"""
+"""Support for Qwikswitch Sensors."""
import logging
from homeassistant.core import callback
from . import DOMAIN as QWIKSWITCH, QSEntity
-DEPENDENCIES = [QWIKSWITCH]
-
_LOGGER = logging.getLogger(__name__)
@@ -33,7 +26,7 @@ class QSSensor(QSEntity):
def __init__(self, sensor):
"""Initialize the sensor."""
- from pyqwikswitch import SENSORS
+ from pyqwikswitch.qwikswitch import SENSORS
super().__init__(sensor['id'], sensor['name'])
self.channel = sensor['channel']
diff --git a/homeassistant/components/qwikswitch/switch.py b/homeassistant/components/qwikswitch/switch.py
index ec544df8c75..2d970a59a2a 100644
--- a/homeassistant/components/qwikswitch/switch.py
+++ b/homeassistant/components/qwikswitch/switch.py
@@ -1,15 +1,8 @@
-"""
-Support for Qwikswitch relays.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.qwikswitch/
-"""
+"""Support for Qwikswitch relays."""
from homeassistant.components.switch import SwitchDevice
from . import DOMAIN as QWIKSWITCH, QSToggleEntity
-DEPENDENCIES = [QWIKSWITCH]
-
async def async_setup_platform(hass, _, add_entities, discovery_info=None):
"""Add switches from the main Qwikswitch component."""
diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py
index 27827da0182..1452fc6a506 100644
--- a/homeassistant/components/rachio/__init__.py
+++ b/homeassistant/components/rachio/__init__.py
@@ -1,9 +1,4 @@
-"""
-Integration with the Rachio Iro sprinkler system controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/rachio/
-"""
+"""Integration with the Rachio Iro sprinkler system controller."""
import asyncio
import logging
from typing import Optional
@@ -16,8 +11,6 @@ from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
from homeassistant.helpers import discovery, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['rachiopy==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'rachio'
diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py
index 9cf57ea3230..ade930b00bc 100644
--- a/homeassistant/components/rachio/binary_sensor.py
+++ b/homeassistant/components/rachio/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Integration with the Rachio Iro sprinkler system controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.rachio/
-"""
+"""Integration with the Rachio Iro sprinkler system controller."""
from abc import abstractmethod
import logging
@@ -15,8 +10,6 @@ from . import (
SIGNAL_RACHIO_CONTROLLER_UPDATE, STATUS_OFFLINE, STATUS_ONLINE,
SUBTYPE_OFFLINE, SUBTYPE_ONLINE)
-DEPENDENCIES = ['rachio']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/rachio/manifest.json b/homeassistant/components/rachio/manifest.json
new file mode 100644
index 00000000000..30bde9a297d
--- /dev/null
+++ b/homeassistant/components/rachio/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rachio",
+ "name": "Rachio",
+ "documentation": "https://www.home-assistant.io/components/rachio",
+ "requirements": [
+ "rachiopy==0.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py
index fe584441afd..1b650d7281a 100644
--- a/homeassistant/components/rachio/switch.py
+++ b/homeassistant/components/rachio/switch.py
@@ -1,9 +1,4 @@
-"""
-Integration with the Rachio Iro sprinkler system controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.rachio/
-"""
+"""Integration with the Rachio Iro sprinkler system controller."""
from abc import abstractmethod
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ from . import (
SIGNAL_RACHIO_ZONE_UPDATE, SUBTYPE_SLEEP_MODE_OFF, SUBTYPE_SLEEP_MODE_ON,
SUBTYPE_ZONE_COMPLETED, SUBTYPE_ZONE_STARTED, SUBTYPE_ZONE_STOPPED)
-DEPENDENCIES = ['rachio']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ZONE_SUMMARY = 'Summary'
diff --git a/homeassistant/components/radarr/manifest.json b/homeassistant/components/radarr/manifest.json
new file mode 100644
index 00000000000..f12fcf4220c
--- /dev/null
+++ b/homeassistant/components/radarr/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "radarr",
+ "name": "Radarr",
+ "documentation": "https://www.home-assistant.io/components/radarr",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py
index 67695ae0e32..a3932acf862 100644
--- a/homeassistant/components/radarr/sensor.py
+++ b/homeassistant/components/radarr/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Radarr.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.radarr/
-"""
+"""Support for Radarr."""
import logging
import time
from datetime import datetime, timedelta
diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py
index 4132d3c27c7..57cbfc031d7 100644
--- a/homeassistant/components/radiotherm/climate.py
+++ b/homeassistant/components/radiotherm/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for Radio Thermostat wifi-enabled home thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.radiotherm/
-"""
+"""Support for Radio Thermostat wifi-enabled home thermostats."""
import datetime
import logging
@@ -19,8 +14,6 @@ from homeassistant.const import (
STATE_OFF)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['radiotherm==2.0.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
diff --git a/homeassistant/components/radiotherm/manifest.json b/homeassistant/components/radiotherm/manifest.json
new file mode 100644
index 00000000000..002fdb63273
--- /dev/null
+++ b/homeassistant/components/radiotherm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "radiotherm",
+ "name": "Radiotherm",
+ "documentation": "https://www.home-assistant.io/components/radiotherm",
+ "requirements": [
+ "radiotherm==2.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py
index bbce7f752af..410fdcd9273 100644
--- a/homeassistant/components/rainbird/__init__.py
+++ b/homeassistant/components/rainbird/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for Rain Bird Irrigation system LNK WiFi Module.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/rainbird/
-"""
+"""Support for Rain Bird Irrigation system LNK WiFi Module."""
import logging
import voluptuous as vol
@@ -11,8 +6,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (CONF_HOST, CONF_PASSWORD)
-REQUIREMENTS = ['pyrainbird==0.1.6']
-
_LOGGER = logging.getLogger(__name__)
DATA_RAINBIRD = 'rainbird'
diff --git a/homeassistant/components/rainbird/manifest.json b/homeassistant/components/rainbird/manifest.json
new file mode 100644
index 00000000000..24113d62534
--- /dev/null
+++ b/homeassistant/components/rainbird/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rainbird",
+ "name": "Rainbird",
+ "documentation": "https://www.home-assistant.io/components/rainbird",
+ "requirements": [
+ "pyrainbird==0.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rainbird/sensor.py b/homeassistant/components/rainbird/sensor.py
index 3d0de04e53e..5fdf116af9d 100644
--- a/homeassistant/components/rainbird/sensor.py
+++ b/homeassistant/components/rainbird/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Rain Bird Irrigation system LNK WiFi Module.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.rainbird/
-"""
+"""Support for Rain Bird Irrigation system LNK WiFi Module."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_RAINBIRD
-DEPENDENCIES = ['rainbird']
-
_LOGGER = logging.getLogger(__name__)
# sensor_type [ description, unit, icon ]
diff --git a/homeassistant/components/rainbird/switch.py b/homeassistant/components/rainbird/switch.py
index 2031769b343..3ade3bdeadd 100644
--- a/homeassistant/components/rainbird/switch.py
+++ b/homeassistant/components/rainbird/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Rain Bird Irrigation system LNK WiFi Module.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/switch.rainbird/
-"""
+"""Support for Rain Bird Irrigation system LNK WiFi Module."""
import logging
@@ -17,8 +12,6 @@ from homeassistant.helpers import config_validation as cv
from . import DATA_RAINBIRD
-DEPENDENCIES = ['rainbird']
-
DOMAIN = 'rainbird'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/raincloud/__init__.py b/homeassistant/components/raincloud/__init__.py
index 7ccf9f33ada..e3b1a77cfa7 100644
--- a/homeassistant/components/raincloud/__init__.py
+++ b/homeassistant/components/raincloud/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for Melnor RainCloud sprinkler water timer.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/raincloud/
-"""
+"""Support for Melnor RainCloud sprinkler water timer."""
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['raincloudy==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
ALLOWED_WATERING_TIME = [5, 10, 15, 30, 45, 60]
diff --git a/homeassistant/components/raincloud/binary_sensor.py b/homeassistant/components/raincloud/binary_sensor.py
index cb66fc3c6af..37c67989169 100644
--- a/homeassistant/components/raincloud/binary_sensor.py
+++ b/homeassistant/components/raincloud/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Melnor RainCloud sprinkler water timer.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.raincloud/
-"""
+"""Support for Melnor RainCloud sprinkler water timer."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from . import BINARY_SENSORS, DATA_RAINCLOUD, ICON_MAP, RainCloudEntity
-DEPENDENCIES = ['raincloud']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/raincloud/manifest.json b/homeassistant/components/raincloud/manifest.json
new file mode 100644
index 00000000000..4d07f2a3ce4
--- /dev/null
+++ b/homeassistant/components/raincloud/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "raincloud",
+ "name": "Raincloud",
+ "documentation": "https://www.home-assistant.io/components/raincloud",
+ "requirements": [
+ "raincloudy==0.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": ["@vanstinator"]
+}
diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py
index 8bcccf06171..cf0c11e22f6 100644
--- a/homeassistant/components/raincloud/sensor.py
+++ b/homeassistant/components/raincloud/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Melnor RainCloud sprinkler water timer.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.raincloud/
-"""
+"""Support for Melnor RainCloud sprinkler water timer."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.helpers.icon import icon_for_battery_level
from . import DATA_RAINCLOUD, ICON_MAP, SENSORS, RainCloudEntity
-DEPENDENCIES = ['raincloud']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py
index 3901e1e0bd8..e320a956f11 100644
--- a/homeassistant/components/raincloud/switch.py
+++ b/homeassistant/components/raincloud/switch.py
@@ -11,8 +11,6 @@ from . import (
ALLOWED_WATERING_TIME, ATTRIBUTION, CONF_WATERING_TIME, DATA_RAINCLOUD,
DEFAULT_WATERING_TIME, SWITCHES, RainCloudEntity)
-DEPENDENCIES = ['raincloud']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/rainmachine/.translations/ko.json b/homeassistant/components/rainmachine/.translations/ko.json
index 5ce254c4026..4e2df2ca217 100644
--- a/homeassistant/components/rainmachine/.translations/ko.json
+++ b/homeassistant/components/rainmachine/.translations/ko.json
@@ -11,7 +11,7 @@
"password": "\ube44\ubc00\ubc88\ud638",
"port": "\ud3ec\ud2b8"
},
- "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694"
+ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825"
}
},
"title": "RainMachine"
diff --git a/homeassistant/components/rainmachine/.translations/nn.json b/homeassistant/components/rainmachine/.translations/nn.json
new file mode 100644
index 00000000000..14b3c7e4dc4
--- /dev/null
+++ b/homeassistant/components/rainmachine/.translations/nn.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "RainMachine"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py
index 6d986fa5c67..8c058557fc1 100644
--- a/homeassistant/components/rainmachine/__init__.py
+++ b/homeassistant/components/rainmachine/__init__.py
@@ -1,4 +1,5 @@
"""Support for RainMachine devices."""
+import asyncio
import logging
from datetime import timedelta
@@ -14,12 +15,12 @@ from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
+from homeassistant.helpers.service import verify_domain_control
from .config_flow import configured_instances
from .const import (
- DATA_CLIENT, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DEFAULT_SSL, DOMAIN)
-
-REQUIREMENTS = ['regenmaschine==1.4.0']
+ DATA_CLIENT, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DEFAULT_SSL, DOMAIN,
+ OPERATION_RESTRICTIONS_CURRENT, OPERATION_RESTRICTIONS_UNIVERSAL)
_LOGGER = logging.getLogger(__name__)
@@ -157,6 +158,8 @@ async def async_setup_entry(hass, config_entry):
from regenmaschine import login
from regenmaschine.errors import RainMachineError
+ _verify_domain_control = verify_domain_control(hass, DOMAIN)
+
websession = aiohttp_client.async_get_clientsession(hass)
try:
@@ -197,59 +200,70 @@ async def async_setup_entry(hass, config_entry):
refresh,
timedelta(seconds=config_entry.data[CONF_SCAN_INTERVAL]))
- async def disable_program(service):
+ @_verify_domain_control
+ async def disable_program(call):
"""Disable a program."""
await rainmachine.client.programs.disable(
- service.data[CONF_PROGRAM_ID])
+ call.data[CONF_PROGRAM_ID])
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
- async def disable_zone(service):
+ @_verify_domain_control
+ async def disable_zone(call):
"""Disable a zone."""
- await rainmachine.client.zones.disable(service.data[CONF_ZONE_ID])
+ await rainmachine.client.zones.disable(call.data[CONF_ZONE_ID])
async_dispatcher_send(hass, ZONE_UPDATE_TOPIC)
- async def enable_program(service):
+ @_verify_domain_control
+ async def enable_program(call):
"""Enable a program."""
- await rainmachine.client.programs.enable(service.data[CONF_PROGRAM_ID])
+ await rainmachine.client.programs.enable(call.data[CONF_PROGRAM_ID])
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
- async def enable_zone(service):
+ @_verify_domain_control
+ async def enable_zone(call):
"""Enable a zone."""
- await rainmachine.client.zones.enable(service.data[CONF_ZONE_ID])
+ await rainmachine.client.zones.enable(call.data[CONF_ZONE_ID])
async_dispatcher_send(hass, ZONE_UPDATE_TOPIC)
- async def pause_watering(service):
+ @_verify_domain_control
+ async def pause_watering(call):
"""Pause watering for a set number of seconds."""
- await rainmachine.client.watering.pause_all(service.data[CONF_SECONDS])
+ await rainmachine.client.watering.pause_all(call.data[CONF_SECONDS])
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
- async def start_program(service):
+ @_verify_domain_control
+ async def start_program(call):
"""Start a particular program."""
- await rainmachine.client.programs.start(service.data[CONF_PROGRAM_ID])
+ await rainmachine.client.programs.start(call.data[CONF_PROGRAM_ID])
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
- async def start_zone(service):
+ @_verify_domain_control
+ async def start_zone(call):
"""Start a particular zone for a certain amount of time."""
await rainmachine.client.zones.start(
- service.data[CONF_ZONE_ID], service.data[CONF_ZONE_RUN_TIME])
+ call.data[CONF_ZONE_ID], call.data[CONF_ZONE_RUN_TIME])
async_dispatcher_send(hass, ZONE_UPDATE_TOPIC)
- async def stop_all(service):
+ @_verify_domain_control
+ async def stop_all(call):
"""Stop all watering."""
await rainmachine.client.watering.stop_all()
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
- async def stop_program(service):
+ @_verify_domain_control
+ async def stop_program(call):
"""Stop a program."""
- await rainmachine.client.programs.stop(service.data[CONF_PROGRAM_ID])
+ await rainmachine.client.programs.stop(call.data[CONF_PROGRAM_ID])
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
- async def stop_zone(service):
+ @_verify_domain_control
+ async def stop_zone(call):
"""Stop a zone."""
- await rainmachine.client.zones.stop(service.data[CONF_ZONE_ID])
+ await rainmachine.client.zones.stop(call.data[CONF_ZONE_ID])
async_dispatcher_send(hass, ZONE_UPDATE_TOPIC)
- async def unpause_watering(service):
+ @_verify_domain_control
+ async def unpause_watering(call):
"""Unpause watering."""
await rainmachine.client.watering.unpause_all()
async_dispatcher_send(hass, PROGRAM_UPDATE_TOPIC)
@@ -296,17 +310,30 @@ class RainMachine:
"""Initialize."""
self.binary_sensor_conditions = binary_sensor_conditions
self.client = client
+ self.data = {}
self.default_zone_runtime = default_zone_runtime
self.device_mac = self.client.mac
- self.restrictions = {}
self.sensor_conditions = sensor_conditions
async def async_update(self):
"""Update sensor/binary sensor data."""
- self.restrictions.update({
- 'current': await self.client.restrictions.current(),
- 'global': await self.client.restrictions.universal()
- })
+ from regenmaschine.errors import RainMachineError
+
+ tasks = {
+ OPERATION_RESTRICTIONS_CURRENT: self.client.restrictions.current(),
+ OPERATION_RESTRICTIONS_UNIVERSAL:
+ self.client.restrictions.universal(),
+ }
+
+ results = await asyncio.gather(*tasks.values(), return_exceptions=True)
+ for operation, result in zip(tasks, results):
+ if isinstance(result, RainMachineError):
+ _LOGGER.error(
+ 'There was an error while updating %s: %s', operation,
+ result)
+ continue
+
+ self.data[operation] = result
class RainMachineEntity(Entity):
diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py
index 929dbcf314c..57dbcb551ed 100644
--- a/homeassistant/components/rainmachine/binary_sensor.py
+++ b/homeassistant/components/rainmachine/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-This platform provides binary sensors for key RainMachine data.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.rainmachine/
-"""
+"""This platform provides binary sensors for key RainMachine data."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -12,11 +7,11 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import (
BINARY_SENSORS, DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN,
+ OPERATION_RESTRICTIONS_CURRENT, OPERATION_RESTRICTIONS_UNIVERSAL,
SENSOR_UPDATE_TOPIC, TYPE_FREEZE, TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS,
TYPE_HOURLY, TYPE_MONTH, TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY,
RainMachineEntity)
-DEPENDENCIES = ['rainmachine']
_LOGGER = logging.getLogger(__name__)
@@ -85,21 +80,26 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
async def async_update(self):
"""Update the state."""
if self._sensor_type == TYPE_FREEZE:
- self._state = self.rainmachine.restrictions['current']['freeze']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_CURRENT]['freeze']
elif self._sensor_type == TYPE_FREEZE_PROTECTION:
- self._state = self.rainmachine.restrictions['global'][
- 'freezeProtectEnabled']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_UNIVERSAL]['freezeProtectEnabled']
elif self._sensor_type == TYPE_HOT_DAYS:
- self._state = self.rainmachine.restrictions['global'][
- 'hotDaysExtraWatering']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_UNIVERSAL]['hotDaysExtraWatering']
elif self._sensor_type == TYPE_HOURLY:
- self._state = self.rainmachine.restrictions['current']['hourly']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_CURRENT]['hourly']
elif self._sensor_type == TYPE_MONTH:
- self._state = self.rainmachine.restrictions['current']['month']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_CURRENT]['month']
elif self._sensor_type == TYPE_RAINDELAY:
- self._state = self.rainmachine.restrictions['current']['rainDelay']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_CURRENT]['rainDelay']
elif self._sensor_type == TYPE_RAINSENSOR:
- self._state = self.rainmachine.restrictions['current'][
- 'rainSensor']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_CURRENT]['rainSensor']
elif self._sensor_type == TYPE_WEEKDAY:
- self._state = self.rainmachine.restrictions['current']['weekDay']
+ self._state = self.rainmachine.data[
+ OPERATION_RESTRICTIONS_CURRENT]['weekDay']
diff --git a/homeassistant/components/rainmachine/const.py b/homeassistant/components/rainmachine/const.py
index 4d08a871f61..d142467443f 100644
--- a/homeassistant/components/rainmachine/const.py
+++ b/homeassistant/components/rainmachine/const.py
@@ -12,4 +12,7 @@ DEFAULT_PORT = 8080
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_SSL = True
+OPERATION_RESTRICTIONS_CURRENT = 'restrictions.current'
+OPERATION_RESTRICTIONS_UNIVERSAL = 'restrictions.universal'
+
TOPIC_UPDATE = 'update_{0}'
diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json
new file mode 100644
index 00000000000..ad7bdada321
--- /dev/null
+++ b/homeassistant/components/rainmachine/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "rainmachine",
+ "name": "Rainmachine",
+ "documentation": "https://www.home-assistant.io/components/rainmachine",
+ "requirements": [
+ "regenmaschine==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py
index 908daa2c83d..4894bd2ce39 100644
--- a/homeassistant/components/rainmachine/sensor.py
+++ b/homeassistant/components/rainmachine/sensor.py
@@ -1,19 +1,14 @@
-"""
-This platform provides support for sensor data from RainMachine.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.rainmachine/
-"""
+"""This platform provides support for sensor data from RainMachine."""
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import (
- DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, SENSOR_UPDATE_TOPIC, SENSORS,
+ DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN,
+ OPERATION_RESTRICTIONS_UNIVERSAL, SENSOR_UPDATE_TOPIC, SENSORS,
RainMachineEntity)
-DEPENDENCIES = ['rainmachine']
_LOGGER = logging.getLogger(__name__)
@@ -87,5 +82,5 @@ class RainMachineSensor(RainMachineEntity):
async def async_update(self):
"""Update the sensor's state."""
- self._state = self.rainmachine.restrictions['global'][
+ self._state = self.rainmachine.data[OPERATION_RESTRICTIONS_UNIVERSAL][
'freezeProtectTemp']
diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py
index 6b658c0fcbf..2023f1e8f5c 100644
--- a/homeassistant/components/rainmachine/switch.py
+++ b/homeassistant/components/rainmachine/switch.py
@@ -1,9 +1,4 @@
-"""
-This component provides support for RainMachine programs and zones.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/switch.rainmachine/
-"""
+"""This component provides support for RainMachine programs and zones."""
from datetime import datetime
import logging
@@ -17,8 +12,6 @@ from . import (
DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC,
ZONE_UPDATE_TOPIC, RainMachineEntity)
-DEPENDENCIES = ['rainmachine']
-
_LOGGER = logging.getLogger(__name__)
ATTR_NEXT_RUN = 'next_run'
diff --git a/homeassistant/components/random/binary_sensor.py b/homeassistant/components/random/binary_sensor.py
index 9bdc57c6e46..ad8bafaf4c2 100644
--- a/homeassistant/components/random/binary_sensor.py
+++ b/homeassistant/components/random/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for showing random states.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.random/
-"""
+"""Support for showing random states."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/random/manifest.json b/homeassistant/components/random/manifest.json
new file mode 100644
index 00000000000..c184f35734c
--- /dev/null
+++ b/homeassistant/components/random/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "random",
+ "name": "Random",
+ "documentation": "https://www.home-assistant.io/components/random",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/random/sensor.py b/homeassistant/components/random/sensor.py
index 4dec96bec2e..cc412ff7773 100644
--- a/homeassistant/components/random/sensor.py
+++ b/homeassistant/components/random/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for showing random numbers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.random/
-"""
+"""Support for showing random numbers."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/raspihats/__init__.py b/homeassistant/components/raspihats/__init__.py
index 622b98223aa..3b37d48c876 100644
--- a/homeassistant/components/raspihats/__init__.py
+++ b/homeassistant/components/raspihats/__init__.py
@@ -6,8 +6,6 @@ import time
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['raspihats==2.2.3', 'smbus-cffi==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'raspihats'
diff --git a/homeassistant/components/raspihats/binary_sensor.py b/homeassistant/components/raspihats/binary_sensor.py
index 29fa474f781..beaf66334c3 100644
--- a/homeassistant/components/raspihats/binary_sensor.py
+++ b/homeassistant/components/raspihats/binary_sensor.py
@@ -15,8 +15,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['raspihats']
-
DEFAULT_INVERT_LOGIC = False
DEFAULT_DEVICE_CLASS = None
diff --git a/homeassistant/components/raspihats/manifest.json b/homeassistant/components/raspihats/manifest.json
new file mode 100644
index 00000000000..8f5040152a2
--- /dev/null
+++ b/homeassistant/components/raspihats/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "raspihats",
+ "name": "Raspihats",
+ "documentation": "https://www.home-assistant.io/components/raspihats",
+ "requirements": [
+ "raspihats==2.2.3",
+ "smbus-cffi==0.5.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/raspihats/switch.py b/homeassistant/components/raspihats/switch.py
index 93538682ad8..082c8f72811 100644
--- a/homeassistant/components/raspihats/switch.py
+++ b/homeassistant/components/raspihats/switch.py
@@ -14,8 +14,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['raspihats']
-
_CHANNELS_SCHEMA = vol.Schema([{
vol.Required(CONF_INDEX): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
diff --git a/homeassistant/components/raspyrfm/manifest.json b/homeassistant/components/raspyrfm/manifest.json
new file mode 100644
index 00000000000..fee815a7e6b
--- /dev/null
+++ b/homeassistant/components/raspyrfm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "raspyrfm",
+ "name": "Raspyrfm",
+ "documentation": "https://www.home-assistant.io/components/raspyrfm",
+ "requirements": [
+ "raspyrfm-client==1.2.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/raspyrfm/switch.py b/homeassistant/components/raspyrfm/switch.py
index a8a6230fc09..9c44fc850c7 100644
--- a/homeassistant/components/raspyrfm/switch.py
+++ b/homeassistant/components/raspyrfm/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for switch devices that can be controlled using the RaspyRFM rc module.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.raspyrfm/
-"""
+"""Support for switchs that can be controlled using the RaspyRFM rc module."""
import logging
import voluptuous as vol
@@ -14,7 +9,6 @@ from homeassistant.const import (
DEVICE_DEFAULT_NAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['raspyrfm-client==1.2.8']
_LOGGER = logging.getLogger(__name__)
CONF_GATEWAY_MANUFACTURER = 'gateway_manufacturer'
diff --git a/homeassistant/components/recollect_waste/manifest.json b/homeassistant/components/recollect_waste/manifest.json
new file mode 100644
index 00000000000..2cccf32f298
--- /dev/null
+++ b/homeassistant/components/recollect_waste/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "recollect_waste",
+ "name": "Recollect waste",
+ "documentation": "https://www.home-assistant.io/components/recollect_waste",
+ "requirements": [
+ "recollect-waste==1.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py
index 9122973c919..7112d22c00b 100644
--- a/homeassistant/components/recollect_waste/sensor.py
+++ b/homeassistant/components/recollect_waste/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Recollect Waste curbside collection pickup.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/sensor.recollect_waste/
-"""
+"""Support for Recollect Waste curbside collection pickup."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['recollect-waste==1.0.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_PICKUP_TYPES = 'pickup_types'
ATTR_AREA_NAME = 'area_name'
diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py
index 0df1fa42ad4..97654b21c6d 100644
--- a/homeassistant/components/recorder/__init__.py
+++ b/homeassistant/components/recorder/__init__.py
@@ -25,8 +25,6 @@ from . import migration, purge
from .const import DATA_INSTANCE
from .util import session_scope
-REQUIREMENTS = ['sqlalchemy==1.3.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'recorder'
diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json
new file mode 100644
index 00000000000..c466d35e23f
--- /dev/null
+++ b/homeassistant/components/recorder/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "recorder",
+ "name": "Recorder",
+ "documentation": "https://www.home-assistant.io/components/recorder",
+ "requirements": [
+ "sqlalchemy==1.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/recswitch/manifest.json b/homeassistant/components/recswitch/manifest.json
new file mode 100644
index 00000000000..af8e802c5ec
--- /dev/null
+++ b/homeassistant/components/recswitch/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "recswitch",
+ "name": "Recswitch",
+ "documentation": "https://www.home-assistant.io/components/recswitch",
+ "requirements": [
+ "pyrecswitch==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/recswitch/switch.py b/homeassistant/components/recswitch/switch.py
index 636c302cea1..c43064c5674 100644
--- a/homeassistant/components/recswitch/switch.py
+++ b/homeassistant/components/recswitch/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Ankuoo RecSwitch MS6126 devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.recswitch/
-"""
+"""Support for Ankuoo RecSwitch MS6126 devices."""
import logging
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyrecswitch==1.0.2']
-
DEFAULT_NAME = 'RecSwitch {0}'
DATA_RSN = 'RSN'
diff --git a/homeassistant/components/reddit/manifest.json b/homeassistant/components/reddit/manifest.json
new file mode 100644
index 00000000000..72ee7a42ca4
--- /dev/null
+++ b/homeassistant/components/reddit/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "reddit",
+ "name": "Reddit",
+ "documentation": "https://www.home-assistant.io/components/reddit",
+ "requirements": [
+ "praw==6.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py
index 3ba43196551..512fca71599 100644
--- a/homeassistant/components/reddit/sensor.py
+++ b/homeassistant/components/reddit/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_MAXIMUM)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['praw==6.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
diff --git a/homeassistant/components/rejseplanen/manifest.json b/homeassistant/components/rejseplanen/manifest.json
new file mode 100644
index 00000000000..72562399330
--- /dev/null
+++ b/homeassistant/components/rejseplanen/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rejseplanen",
+ "name": "Rejseplanen",
+ "documentation": "https://www.home-assistant.io/components/rejseplanen",
+ "requirements": [
+ "rjpl==0.3.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py
index 7a8cddb6179..0c611e2c1e4 100755
--- a/homeassistant/components/rejseplanen/sensor.py
+++ b/homeassistant/components/rejseplanen/sensor.py
@@ -19,7 +19,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['rjpl==0.3.5']
_LOGGER = logging.getLogger(__name__)
ATTR_STOP_ID = 'Stop ID'
diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py
index 82619e35a0e..93f28b527ba 100644
--- a/homeassistant/components/remember_the_milk/__init__.py
+++ b/homeassistant/components/remember_the_milk/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.helpers.entity_component import EntityComponent
# httplib2 is a transitive dependency from RtmAPI. If this dependency is not
# set explicitly, the library does not work.
-REQUIREMENTS = ['RtmAPI==0.7.0', 'httplib2==0.10.3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'remember_the_milk'
diff --git a/homeassistant/components/remember_the_milk/manifest.json b/homeassistant/components/remember_the_milk/manifest.json
new file mode 100644
index 00000000000..c9d35e9d2c9
--- /dev/null
+++ b/homeassistant/components/remember_the_milk/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "remember_the_milk",
+ "name": "Remember the milk",
+ "documentation": "https://www.home-assistant.io/components/remember_the_milk",
+ "requirements": [
+ "RtmAPI==0.7.0",
+ "httplib2==0.10.3"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py
index de79adc9f0e..f08abf5fd4a 100644
--- a/homeassistant/components/remote/__init__.py
+++ b/homeassistant/components/remote/__init__.py
@@ -26,7 +26,6 @@ ATTR_DELAY_SECS = 'delay_secs'
ATTR_HOLD_SECS = 'hold_secs'
DOMAIN = 'remote'
-DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_ALL_REMOTES = group.ENTITY_ID_FORMAT.format('all_remotes')
diff --git a/homeassistant/components/remote/manifest.json b/homeassistant/components/remote/manifest.json
new file mode 100644
index 00000000000..5fe585dcd83
--- /dev/null
+++ b/homeassistant/components/remote/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "remote",
+ "name": "Remote",
+ "documentation": "https://www.home-assistant.io/components/remote",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py
index 1a94159290f..0d28e98229c 100644
--- a/homeassistant/components/rest/binary_sensor.py
+++ b/homeassistant/components/rest/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for RESTful binary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.rest/
-"""
+"""Support for RESTful binary sensors."""
import logging
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
diff --git a/homeassistant/components/rest/manifest.json b/homeassistant/components/rest/manifest.json
new file mode 100644
index 00000000000..999f5740715
--- /dev/null
+++ b/homeassistant/components/rest/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "rest",
+ "name": "Rest",
+ "documentation": "https://www.home-assistant.io/components/rest",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rest/notify.py b/homeassistant/components/rest/notify.py
index de75db83848..8134e73ae6b 100644
--- a/homeassistant/components/rest/notify.py
+++ b/homeassistant/components/rest/notify.py
@@ -1,9 +1,4 @@
-"""
-RESTful platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.rest/
-"""
+"""RESTful platform for notify component."""
import logging
import requests
diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py
index a9446ee3503..fe92f9d8a10 100644
--- a/homeassistant/components/rest/sensor.py
+++ b/homeassistant/components/rest/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for RESTful API sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.rest/
-"""
+"""Support for RESTful API sensors."""
import logging
import json
diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py
index 5f1920ae1af..2ef45b226fe 100644
--- a/homeassistant/components/rest/switch.py
+++ b/homeassistant/components/rest/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for RESTful switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.rest/
-"""
+"""Support for RESTful switches."""
import asyncio
import logging
diff --git a/homeassistant/components/rest_command/manifest.json b/homeassistant/components/rest_command/manifest.json
new file mode 100644
index 00000000000..ced930fc64f
--- /dev/null
+++ b/homeassistant/components/rest_command/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "rest_command",
+ "name": "Rest command",
+ "documentation": "https://www.home-assistant.io/components/rest_command",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rest_command/services.yaml b/homeassistant/components/rest_command/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py
index 98e80580fea..1dbfd208c64 100644
--- a/homeassistant/components/rflink/__init__.py
+++ b/homeassistant/components/rflink/__init__.py
@@ -18,8 +18,6 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.restore_state import RestoreEntity
-REQUIREMENTS = ['rflink==0.0.37']
-
_LOGGER = logging.getLogger(__name__)
ATTR_EVENT = 'event'
diff --git a/homeassistant/components/rflink/binary_sensor.py b/homeassistant/components/rflink/binary_sensor.py
index 5318642a5b1..ae9f282be0a 100644
--- a/homeassistant/components/rflink/binary_sensor.py
+++ b/homeassistant/components/rflink/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Rflink binary sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.rflink/
-"""
+"""Support for Rflink binary sensors."""
import logging
import voluptuous as vol
@@ -19,8 +14,6 @@ from . import CONF_ALIASES, CONF_DEVICES, RflinkDevice
CONF_OFF_DELAY = 'off_delay'
DEFAULT_FORCE_UPDATE = False
-DEPENDENCIES = ['rflink']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -58,7 +51,7 @@ class RflinkBinarySensor(RflinkDevice, BinarySensorDevice):
"""Representation of an Rflink binary sensor."""
def __init__(self, device_id, device_class=None,
- force_update=None, off_delay=None,
+ force_update=False, off_delay=None,
**kwargs):
"""Handle sensor specific args and super init."""
self._state = None
diff --git a/homeassistant/components/rflink/cover.py b/homeassistant/components/rflink/cover.py
index f91ef1cc682..d78fe8312e7 100644
--- a/homeassistant/components/rflink/cover.py
+++ b/homeassistant/components/rflink/cover.py
@@ -1,9 +1,4 @@
-"""
-Support for Rflink Cover devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/cover.rflink/
-"""
+"""Support for Rflink Cover devices."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from . import (
CONF_GROUP, CONF_GROUP_ALIASES, CONF_NOGROUP_ALIASES,
CONF_SIGNAL_REPETITIONS, DEVICE_DEFAULTS_SCHEMA, RflinkCommand)
-DEPENDENCIES = ['rflink']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py
index cdb34328b51..d3ef73a09bb 100644
--- a/homeassistant/components/rflink/light.py
+++ b/homeassistant/components/rflink/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Rflink lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.rflink/
-"""
+"""Support for Rflink lights."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from . import (
CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER, DEVICE_DEFAULTS_SCHEMA,
EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, remove_deprecated)
-DEPENDENCIES = ['rflink']
-
_LOGGER = logging.getLogger(__name__)
TYPE_DIMMABLE = 'dimmable'
diff --git a/homeassistant/components/rflink/manifest.json b/homeassistant/components/rflink/manifest.json
new file mode 100644
index 00000000000..a3b81f39c55
--- /dev/null
+++ b/homeassistant/components/rflink/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rflink",
+ "name": "Rflink",
+ "documentation": "https://www.home-assistant.io/components/rflink",
+ "requirements": [
+ "rflink==0.0.37"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rflink/sensor.py b/homeassistant/components/rflink/sensor.py
index e46cc09d0ba..1e3a18572ff 100644
--- a/homeassistant/components/rflink/sensor.py
+++ b/homeassistant/components/rflink/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Rflink sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.rflink/
-"""
+"""Support for Rflink sensors."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from . import (
EVENT_KEY_UNIT, SIGNAL_AVAILABILITY, SIGNAL_HANDLE_EVENT, TMP_ENTITY,
RflinkDevice, remove_deprecated)
-DEPENDENCIES = ['rflink']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_ICONS = {
diff --git a/homeassistant/components/rflink/switch.py b/homeassistant/components/rflink/switch.py
index a1470bf115f..63f506cc13b 100644
--- a/homeassistant/components/rflink/switch.py
+++ b/homeassistant/components/rflink/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Rflink switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.rflink/
-"""
+"""Support for Rflink switches."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from . import (
CONF_NOGROUP_ALIASES, CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS,
DEVICE_DEFAULTS_SCHEMA, SwitchableRflinkDevice, remove_deprecated)
-DEPENDENCIES = ['rflink']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py
index 411f0538bde..f7e42ce4357 100644
--- a/homeassistant/components/rfxtrx/__init__.py
+++ b/homeassistant/components/rfxtrx/__init__.py
@@ -12,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
-REQUIREMENTS = ['pyRFXtrx==0.23']
-
DOMAIN = 'rfxtrx'
DEFAULT_SIGNAL_REPETITIONS = 1
diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py
index d548897fb80..ec32f12bc68 100644
--- a/homeassistant/components/rfxtrx/binary_sensor.py
+++ b/homeassistant/components/rfxtrx/binary_sensor.py
@@ -17,8 +17,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['rfxtrx']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({
diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py
index 7ac0e2aa43f..a915c48818a 100644
--- a/homeassistant/components/rfxtrx/cover.py
+++ b/homeassistant/components/rfxtrx/cover.py
@@ -10,8 +10,6 @@ from . import (
CONF_AUTOMATIC_ADD, CONF_DEVICES, CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS,
DEFAULT_SIGNAL_REPETITIONS)
-DEPENDENCIES = ['rfxtrx']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({
diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py
index 3320a67214e..56f0c21117d 100644
--- a/homeassistant/components/rfxtrx/light.py
+++ b/homeassistant/components/rfxtrx/light.py
@@ -13,8 +13,6 @@ from . import (
CONF_AUTOMATIC_ADD, CONF_DEVICES, CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS,
DEFAULT_SIGNAL_REPETITIONS)
-DEPENDENCIES = ['rfxtrx']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/rfxtrx/manifest.json b/homeassistant/components/rfxtrx/manifest.json
new file mode 100644
index 00000000000..5d6cd4b038c
--- /dev/null
+++ b/homeassistant/components/rfxtrx/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "rfxtrx",
+ "name": "Rfxtrx",
+ "documentation": "https://www.home-assistant.io/components/rfxtrx",
+ "requirements": [
+ "pyRFXtrx==0.23"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py
index cc54320cb67..94d836fa45c 100644
--- a/homeassistant/components/rfxtrx/sensor.py
+++ b/homeassistant/components/rfxtrx/sensor.py
@@ -14,8 +14,6 @@ from . import (
ATTR_DATA_TYPE, ATTR_FIRE_EVENT, CONF_AUTOMATIC_ADD, CONF_DATA_TYPE,
CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES)
-DEPENDENCIES = ['rfxtrx']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py
index 908c07ea745..938b575eca0 100644
--- a/homeassistant/components/rfxtrx/switch.py
+++ b/homeassistant/components/rfxtrx/switch.py
@@ -12,8 +12,6 @@ from . import (
CONF_AUTOMATIC_ADD, CONF_DEVICES, CONF_FIRE_EVENT, CONF_SIGNAL_REPETITIONS,
DEFAULT_SIGNAL_REPETITIONS)
-DEPENDENCIES = ['rfxtrx']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py
index 74da7a9d542..669e91a1302 100644
--- a/homeassistant/components/ring/__init__.py
+++ b/homeassistant/components/ring/__init__.py
@@ -7,8 +7,6 @@ import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['ring_doorbell==0.2.3']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Ring.com"
diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py
index bcc365a2e83..a12954f6c29 100644
--- a/homeassistant/components/ring/binary_sensor.py
+++ b/homeassistant/components/ring/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-This component provides HA sensor support for Ring Door Bell/Chimes.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.ring/
-"""
+"""This component provides HA sensor support for Ring Door Bell/Chimes."""
from datetime import timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from . import ATTRIBUTION, DATA_RING, DEFAULT_ENTITY_NAMESPACE
-DEPENDENCIES = ['ring']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py
index 8970e61b1a1..2a680a63b78 100644
--- a/homeassistant/components/ring/camera.py
+++ b/homeassistant/components/ring/camera.py
@@ -1,9 +1,4 @@
-"""
-This component provides support to the Ring Door Bell camera.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.ring/
-"""
+"""This component provides support to the Ring Door Bell camera."""
import asyncio
from datetime import timedelta
import logging
@@ -21,8 +16,6 @@ from . import ATTRIBUTION, DATA_RING, NOTIFICATION_ID
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
-DEPENDENCIES = ['ring', 'ffmpeg']
-
FORCE_REFRESH_INTERVAL = timedelta(minutes=45)
_LOGGER = logging.getLogger(__name__)
@@ -157,14 +150,23 @@ class RingCam(Camera):
self._camera.update()
self._utcnow = dt_util.utcnow()
- last_recording_id = self._camera.last_recording_id
+ try:
+ last_event = self._camera.history(limit=1)[0]
+ except (IndexError, TypeError):
+ return
- if self._last_video_id != last_recording_id or \
- self._utcnow >= self._expires_at:
+ last_recording_id = last_event['id']
+ video_status = last_event['recording']['status']
- _LOGGER.info("Ring DoorBell properties refreshed")
+ if video_status == 'ready' and \
+ (self._last_video_id != last_recording_id or
+ self._utcnow >= self._expires_at):
- # update attributes if new video or if URL has expired
- self._last_video_id = self._camera.last_recording_id
- self._video_url = self._camera.recording_url(self._last_video_id)
- self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow
+ video_url = self._camera.recording_url(last_recording_id)
+ if video_url:
+ _LOGGER.info("Ring DoorBell properties refreshed")
+
+ # update attributes if new video or if URL has expired
+ self._last_video_id = last_recording_id
+ self._video_url = video_url
+ self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow
diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json
new file mode 100644
index 00000000000..9dbedad1ffc
--- /dev/null
+++ b/homeassistant/components/ring/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ring",
+ "name": "Ring",
+ "documentation": "https://www.home-assistant.io/components/ring",
+ "requirements": [
+ "ring_doorbell==0.2.3"
+ ],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py
index 5e323d89ad8..8b36cf70ea3 100644
--- a/homeassistant/components/ring/sensor.py
+++ b/homeassistant/components/ring/sensor.py
@@ -1,9 +1,4 @@
-"""
-This component provides HA sensor support for Ring Door Bell/Chimes.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.ring/
-"""
+"""This component provides HA sensor support for Ring Door Bell/Chimes."""
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ from homeassistant.helpers.icon import icon_for_battery_level
from . import ATTRIBUTION, DATA_RING, DEFAULT_ENTITY_NAMESPACE
-DEPENDENCIES = ['ring']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=30)
diff --git a/homeassistant/components/ripple/manifest.json b/homeassistant/components/ripple/manifest.json
new file mode 100644
index 00000000000..fe93bf02445
--- /dev/null
+++ b/homeassistant/components/ripple/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ripple",
+ "name": "Ripple",
+ "documentation": "https://www.home-assistant.io/components/ripple",
+ "requirements": [
+ "python-ripple-api==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ripple/sensor.py b/homeassistant/components/ripple/sensor.py
index 54530571c3e..773a6d77f54 100644
--- a/homeassistant/components/ripple/sensor.py
+++ b/homeassistant/components/ripple/sensor.py
@@ -8,8 +8,6 @@ from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-ripple-api==0.0.3']
-
ATTRIBUTION = "Data provided by ripple.com"
DEFAULT_NAME = 'Ripple Balance'
diff --git a/homeassistant/components/ritassist/device_tracker.py b/homeassistant/components/ritassist/device_tracker.py
index c41ae9f2ec2..69bf2454f86 100644
--- a/homeassistant/components/ritassist/device_tracker.py
+++ b/homeassistant/components/ritassist/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for RitAssist Platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.ritassist/
-"""
+"""Support for RitAssist Platform."""
import logging
import requests
@@ -14,8 +9,6 @@ from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers.event import track_utc_time_change
-REQUIREMENTS = ['ritassist==0.9.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_CLIENT_ID = 'client_id'
diff --git a/homeassistant/components/ritassist/manifest.json b/homeassistant/components/ritassist/manifest.json
new file mode 100644
index 00000000000..af8464e4e93
--- /dev/null
+++ b/homeassistant/components/ritassist/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ritassist",
+ "name": "Ritassist",
+ "documentation": "https://www.home-assistant.io/components/ritassist",
+ "requirements": [
+ "ritassist==0.9.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rmvtransport/manifest.json b/homeassistant/components/rmvtransport/manifest.json
new file mode 100644
index 00000000000..3f32a61c081
--- /dev/null
+++ b/homeassistant/components/rmvtransport/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "rmvtransport",
+ "name": "Rmvtransport",
+ "documentation": "https://www.home-assistant.io/components/rmvtransport",
+ "requirements": [
+ "PyRMVtransport==0.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@cgtobi"
+ ]
+}
diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py
index 7835b74ac98..13d13281bb6 100644
--- a/homeassistant/components/rmvtransport/sensor.py
+++ b/homeassistant/components/rmvtransport/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for real-time departure information for Rhein-Main public transport.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.rmvtransport/
-"""
+"""Support for departure information for Rhein-Main public transport."""
import asyncio
import logging
from datetime import timedelta
@@ -18,8 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['PyRMVtransport==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_NEXT_DEPARTURE = 'next_departure'
diff --git a/homeassistant/components/rocketchat/manifest.json b/homeassistant/components/rocketchat/manifest.json
new file mode 100644
index 00000000000..3a8959f1be6
--- /dev/null
+++ b/homeassistant/components/rocketchat/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rocketchat",
+ "name": "Rocketchat",
+ "documentation": "https://www.home-assistant.io/components/rocketchat",
+ "requirements": [
+ "rocketchat-API==0.6.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rocketchat/notify.py b/homeassistant/components/rocketchat/notify.py
index 8bf1e172264..bbe02698c8e 100644
--- a/homeassistant/components/rocketchat/notify.py
+++ b/homeassistant/components/rocketchat/notify.py
@@ -1,9 +1,4 @@
-"""
-Rocket.Chat notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.rocketchat/
-"""
+"""Rocket.Chat notification service."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (ATTR_DATA, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['rocketchat-API==0.6.1']
-
_LOGGER = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter
diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py
index 89bb1a9acb8..3444f8a3183 100644
--- a/homeassistant/components/roku/__init__.py
+++ b/homeassistant/components/roku/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_HOST
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-roku==3.1.5']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'roku'
diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json
new file mode 100644
index 00000000000..7f7befbe418
--- /dev/null
+++ b/homeassistant/components/roku/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "roku",
+ "name": "Roku",
+ "documentation": "https://www.home-assistant.io/components/roku",
+ "requirements": [
+ "python-roku==3.1.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py
index 3cf27af0674..41eff531de3 100644
--- a/homeassistant/components/roku/media_player.py
+++ b/homeassistant/components/roku/media_player.py
@@ -10,8 +10,6 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import (CONF_HOST, STATE_HOME, STATE_IDLE,
STATE_PLAYING)
-DEPENDENCIES = ['roku']
-
DEFAULT_PORT = 8060
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py
index 5529918010c..59ecbe351ad 100644
--- a/homeassistant/components/roku/remote.py
+++ b/homeassistant/components/roku/remote.py
@@ -4,8 +4,6 @@ import requests.exceptions
from homeassistant.components import remote
from homeassistant.const import (CONF_HOST)
-DEPENDENCIES = ['roku']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/roku/services.yaml b/homeassistant/components/roku/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json
new file mode 100644
index 00000000000..058ad0c5e81
--- /dev/null
+++ b/homeassistant/components/roomba/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "roomba",
+ "name": "Roomba",
+ "documentation": "https://www.home-assistant.io/components/roomba",
+ "requirements": [
+ "roombapy==1.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@pschmitt"
+ ]
+}
diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py
index d06ecc5141f..e9f62d3bc17 100644
--- a/homeassistant/components/roomba/vacuum.py
+++ b/homeassistant/components/roomba/vacuum.py
@@ -1,9 +1,4 @@
-"""
-Support for Wi-Fi enabled iRobot Roombas.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/vacuum.roomba/
-"""
+"""Support for Wi-Fi enabled iRobot Roombas."""
import asyncio
import logging
@@ -19,8 +14,6 @@ from homeassistant.const import (
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['roombapy==1.3.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_BIN_FULL = 'bin_full'
diff --git a/homeassistant/components/route53/__init__.py b/homeassistant/components/route53/__init__.py
index 725dec8b8e5..43a7b75b94d 100644
--- a/homeassistant/components/route53/__init__.py
+++ b/homeassistant/components/route53/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_DOMAIN, CONF_TTL, CONF_ZONE
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['boto3==1.9.16', 'ipify==1.0.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_ACCESS_KEY_ID = 'aws_access_key_id'
diff --git a/homeassistant/components/route53/manifest.json b/homeassistant/components/route53/manifest.json
new file mode 100644
index 00000000000..d377ca7dca0
--- /dev/null
+++ b/homeassistant/components/route53/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "route53",
+ "name": "Route53",
+ "documentation": "https://www.home-assistant.io/components/route53",
+ "requirements": [
+ "boto3==1.9.16",
+ "ipify==1.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/route53/services.yaml b/homeassistant/components/route53/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/rova/manifest.json b/homeassistant/components/rova/manifest.json
new file mode 100644
index 00000000000..71ec8fcbc9b
--- /dev/null
+++ b/homeassistant/components/rova/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rova",
+ "name": "Rova",
+ "documentation": "https://www.home-assistant.io/components/rova",
+ "requirements": [
+ "rova==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py
index 07be331f23f..dcdee08734c 100644
--- a/homeassistant/components/rova/sensor.py
+++ b/homeassistant/components/rova/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Rova garbage calendar.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.rova/
-"""
+"""Support for Rova garbage calendar."""
from datetime import datetime, timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['rova==0.1.0']
-
# Config for rova requests.
CONF_ZIP_CODE = 'zip_code'
CONF_HOUSE_NUMBER = 'house_number'
diff --git a/homeassistant/components/rpi_camera/camera.py b/homeassistant/components/rpi_camera/camera.py
index ba6f5e93304..f0dd1d36539 100644
--- a/homeassistant/components/rpi_camera/camera.py
+++ b/homeassistant/components/rpi_camera/camera.py
@@ -1,9 +1,4 @@
-"""
-Camera platform that has a Raspberry Pi camera.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.rpi_camera/
-"""
+"""Camera platform that has a Raspberry Pi camera."""
import os
import subprocess
import logging
diff --git a/homeassistant/components/rpi_camera/manifest.json b/homeassistant/components/rpi_camera/manifest.json
new file mode 100644
index 00000000000..1f905b103fe
--- /dev/null
+++ b/homeassistant/components/rpi_camera/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "rpi_camera",
+ "name": "Rpi camera",
+ "documentation": "https://www.home-assistant.io/components/rpi_camera",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rpi_gpio/__init__.py b/homeassistant/components/rpi_gpio/__init__.py
index b5bd0796f16..e568281edb1 100644
--- a/homeassistant/components/rpi_gpio/__init__.py
+++ b/homeassistant/components/rpi_gpio/__init__.py
@@ -4,8 +4,6 @@ import logging
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['RPi.GPIO==0.6.5']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'rpi_gpio'
diff --git a/homeassistant/components/rpi_gpio/binary_sensor.py b/homeassistant/components/rpi_gpio/binary_sensor.py
index 559ae958404..d9903350aa0 100644
--- a/homeassistant/components/rpi_gpio/binary_sensor.py
+++ b/homeassistant/components/rpi_gpio/binary_sensor.py
@@ -20,8 +20,6 @@ DEFAULT_BOUNCETIME = 50
DEFAULT_INVERT_LOGIC = False
DEFAULT_PULL_MODE = 'UP'
-DEPENDENCIES = ['rpi_gpio']
-
_SENSORS_SCHEMA = vol.Schema({
cv.positive_int: cv.string,
})
diff --git a/homeassistant/components/rpi_gpio/cover.py b/homeassistant/components/rpi_gpio/cover.py
index 403f7ec6867..d841dec777e 100644
--- a/homeassistant/components/rpi_gpio/cover.py
+++ b/homeassistant/components/rpi_gpio/cover.py
@@ -23,8 +23,6 @@ DEFAULT_RELAY_TIME = .2
DEFAULT_STATE_PULL_MODE = 'UP'
DEFAULT_INVERT_STATE = False
DEFAULT_INVERT_RELAY = False
-DEPENDENCIES = ['rpi_gpio']
-
_COVERS_SCHEMA = vol.All(
cv.ensure_list,
[
diff --git a/homeassistant/components/rpi_gpio/manifest.json b/homeassistant/components/rpi_gpio/manifest.json
new file mode 100644
index 00000000000..88322708b27
--- /dev/null
+++ b/homeassistant/components/rpi_gpio/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rpi_gpio",
+ "name": "Rpi gpio",
+ "documentation": "https://www.home-assistant.io/components/rpi_gpio",
+ "requirements": [
+ "RPi.GPIO==0.6.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rpi_gpio/switch.py b/homeassistant/components/rpi_gpio/switch.py
index bdb79d03eec..ba713e5d273 100644
--- a/homeassistant/components/rpi_gpio/switch.py
+++ b/homeassistant/components/rpi_gpio/switch.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['rpi_gpio']
-
CONF_PULL_MODE = 'pull_mode'
CONF_PORTS = 'ports'
CONF_INVERT_LOGIC = 'invert_logic'
diff --git a/homeassistant/components/rpi_gpio_pwm/light.py b/homeassistant/components/rpi_gpio_pwm/light.py
index b0b9ef1b763..73ce2a67306 100644
--- a/homeassistant/components/rpi_gpio_pwm/light.py
+++ b/homeassistant/components/rpi_gpio_pwm/light.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
from homeassistant.helpers.restore_state import RestoreEntity
-REQUIREMENTS = ['pwmled==1.4.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_LEDS = 'leds'
diff --git a/homeassistant/components/rpi_gpio_pwm/manifest.json b/homeassistant/components/rpi_gpio_pwm/manifest.json
new file mode 100644
index 00000000000..d2ed380d68a
--- /dev/null
+++ b/homeassistant/components/rpi_gpio_pwm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rpi_gpio_pwm",
+ "name": "Rpi gpio pwm",
+ "documentation": "https://www.home-assistant.io/components/rpi_gpio_pwm",
+ "requirements": [
+ "pwmled==1.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rpi_pfio/__init__.py b/homeassistant/components/rpi_pfio/__init__.py
index b096d9fe98a..8341ebffcee 100644
--- a/homeassistant/components/rpi_pfio/__init__.py
+++ b/homeassistant/components/rpi_pfio/__init__.py
@@ -4,8 +4,6 @@ import logging
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['pifacecommon==4.2.2', 'pifacedigitalio==3.0.5']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'rpi_pfio'
diff --git a/homeassistant/components/rpi_pfio/binary_sensor.py b/homeassistant/components/rpi_pfio/binary_sensor.py
index 677ec3bb16f..b298c3dc44a 100644
--- a/homeassistant/components/rpi_pfio/binary_sensor.py
+++ b/homeassistant/components/rpi_pfio/binary_sensor.py
@@ -18,8 +18,6 @@ CONF_SETTLE_TIME = 'settle_time'
DEFAULT_INVERT_LOGIC = False
DEFAULT_SETTLE_TIME = 20
-DEPENDENCIES = ['rpi_pfio']
-
PORT_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
diff --git a/homeassistant/components/rpi_pfio/manifest.json b/homeassistant/components/rpi_pfio/manifest.json
new file mode 100644
index 00000000000..7fc724bf90a
--- /dev/null
+++ b/homeassistant/components/rpi_pfio/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "rpi_pfio",
+ "name": "Rpi pfio",
+ "documentation": "https://www.home-assistant.io/components/rpi_pfio",
+ "requirements": [
+ "pifacecommon==4.2.2",
+ "pifacedigitalio==3.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rpi_pfio/switch.py b/homeassistant/components/rpi_pfio/switch.py
index fc158bd666f..5a69ec8706f 100644
--- a/homeassistant/components/rpi_pfio/switch.py
+++ b/homeassistant/components/rpi_pfio/switch.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['rpi_pfio']
-
ATTR_INVERT_LOGIC = 'invert_logic'
CONF_PORTS = 'ports'
diff --git a/homeassistant/components/rpi_rf/manifest.json b/homeassistant/components/rpi_rf/manifest.json
new file mode 100644
index 00000000000..e5fffee131e
--- /dev/null
+++ b/homeassistant/components/rpi_rf/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rpi_rf",
+ "name": "Rpi rf",
+ "documentation": "https://www.home-assistant.io/components/rpi_rf",
+ "requirements": [
+ "rpi-rf==0.9.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rpi_rf/switch.py b/homeassistant/components/rpi_rf/switch.py
index 6844cb0f383..6531e42bd23 100644
--- a/homeassistant/components/rpi_rf/switch.py
+++ b/homeassistant/components/rpi_rf/switch.py
@@ -1,9 +1,4 @@
-"""
-Allows to configure a switch using a 433MHz module via GPIO on a Raspberry Pi.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.rpi_rf/
-"""
+"""Support for a switch using a 433MHz module via GPIO on a Raspberry Pi."""
import importlib
import logging
@@ -14,8 +9,6 @@ from homeassistant.const import (
CONF_NAME, CONF_SWITCHES, EVENT_HOMEASSISTANT_STOP)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['rpi-rf==0.9.7']
-
_LOGGER = logging.getLogger(__name__)
CONF_CODE_OFF = 'code_off'
diff --git a/homeassistant/components/rss_feed_template/__init__.py b/homeassistant/components/rss_feed_template/__init__.py
index 3c93fe2ac83..1d82d40ba72 100644
--- a/homeassistant/components/rss_feed_template/__init__.py
+++ b/homeassistant/components/rss_feed_template/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.components.http import HomeAssistantView
import homeassistant.helpers.config_validation as cv
CONTENT_TYPE_XML = 'text/xml'
-DEPENDENCIES = ['http']
-
DOMAIN = 'rss_feed_template'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/rss_feed_template/manifest.json b/homeassistant/components/rss_feed_template/manifest.json
new file mode 100644
index 00000000000..c92f6b2a0ba
--- /dev/null
+++ b/homeassistant/components/rss_feed_template/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "rss_feed_template",
+ "name": "Rss feed template",
+ "documentation": "https://www.home-assistant.io/components/rss_feed_template",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/rtorrent/manifest.json b/homeassistant/components/rtorrent/manifest.json
new file mode 100644
index 00000000000..ce2dca9e085
--- /dev/null
+++ b/homeassistant/components/rtorrent/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "rtorrent",
+ "name": "Rtorrent",
+ "documentation": "https://www.home-assistant.io/components/rtorrent",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/russound_rio/manifest.json b/homeassistant/components/russound_rio/manifest.json
new file mode 100644
index 00000000000..af81d9c031a
--- /dev/null
+++ b/homeassistant/components/russound_rio/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "russound_rio",
+ "name": "Russound rio",
+ "documentation": "https://www.home-assistant.io/components/russound_rio",
+ "requirements": [
+ "russound_rio==0.1.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/russound_rio/media_player.py b/homeassistant/components/russound_rio/media_player.py
index 972594e07e6..e2462a2c2b4 100644
--- a/homeassistant/components/russound_rio/media_player.py
+++ b/homeassistant/components/russound_rio/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Russound multizone controllers using RIO Protocol.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.russound_rio/
-"""
+"""Support for Russound multizone controllers using RIO Protocol."""
import logging
import voluptuous as vol
@@ -19,8 +14,6 @@ from homeassistant.const import (
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['russound_rio==0.1.4']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_RUSSOUND = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
diff --git a/homeassistant/components/russound_rnet/manifest.json b/homeassistant/components/russound_rnet/manifest.json
new file mode 100644
index 00000000000..716f383040f
--- /dev/null
+++ b/homeassistant/components/russound_rnet/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "russound_rnet",
+ "name": "Russound rnet",
+ "documentation": "https://www.home-assistant.io/components/russound_rnet",
+ "requirements": [
+ "russound==0.1.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/russound_rnet/media_player.py b/homeassistant/components/russound_rnet/media_player.py
index 6d919cdf7a8..788b5da361b 100644
--- a/homeassistant/components/russound_rnet/media_player.py
+++ b/homeassistant/components/russound_rnet/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing with Russound via RNET Protocol.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.russound_rnet/
-"""
+"""Support for interfacing with Russound via RNET Protocol."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['russound==0.1.9']
-
_LOGGER = logging.getLogger(__name__)
CONF_ZONES = 'zones'
diff --git a/homeassistant/components/ruter/manifest.json b/homeassistant/components/ruter/manifest.json
new file mode 100644
index 00000000000..57688d0e025
--- /dev/null
+++ b/homeassistant/components/ruter/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "ruter",
+ "name": "Ruter",
+ "documentation": "https://www.home-assistant.io/components/ruter",
+ "requirements": [
+ "pyruter==1.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/ruter/sensor.py b/homeassistant/components/ruter/sensor.py
index 91966f0df9c..fd72d59dbea 100644
--- a/homeassistant/components/ruter/sensor.py
+++ b/homeassistant/components/ruter/sensor.py
@@ -1,9 +1,4 @@
-"""
-A sensor platform that give you information about next departures from Ruter.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/sensor.ruter/
-"""
+"""A sensor to provide information about next departures from Ruter."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.const import CONF_NAME
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['pyruter==1.1.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_STOP_ID = 'stop_id'
diff --git a/homeassistant/components/sabnzbd/__init__.py b/homeassistant/components/sabnzbd/__init__.py
index d070872f85c..4275765a1bf 100644
--- a/homeassistant/components/sabnzbd/__init__.py
+++ b/homeassistant/components/sabnzbd/__init__.py
@@ -15,8 +15,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['pysabnzbd==1.1.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'sabnzbd'
diff --git a/homeassistant/components/sabnzbd/manifest.json b/homeassistant/components/sabnzbd/manifest.json
new file mode 100644
index 00000000000..9424e5f3a1a
--- /dev/null
+++ b/homeassistant/components/sabnzbd/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sabnzbd",
+ "name": "Sabnzbd",
+ "documentation": "https://www.home-assistant.io/components/sabnzbd",
+ "requirements": [
+ "pysabnzbd==1.1.0"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py
index 4968725a4be..3c57a844431 100644
--- a/homeassistant/components/sabnzbd/sensor.py
+++ b/homeassistant/components/sabnzbd/sensor.py
@@ -6,8 +6,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_SABNZBD, SENSOR_TYPES, SIGNAL_SABNZBD_UPDATED
-DEPENDENCIES = ['sabnzbd']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/sabnzbd/services.yaml b/homeassistant/components/sabnzbd/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json
new file mode 100644
index 00000000000..c8825f4ac3f
--- /dev/null
+++ b/homeassistant/components/samsungtv/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "samsungtv",
+ "name": "Samsungtv",
+ "documentation": "https://www.home-assistant.io/components/samsungtv",
+ "requirements": [
+ "samsungctl[websocket]==0.7.1",
+ "wakeonlan==1.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py
index e6715669da7..05921d7e84b 100644
--- a/homeassistant/components/samsungtv/media_player.py
+++ b/homeassistant/components/samsungtv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with an Samsung TV.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.samsungtv/
-"""
+"""Support for interface with an Samsung TV."""
import asyncio
from datetime import timedelta
import logging
@@ -23,8 +18,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as dt_util
-REQUIREMENTS = ['samsungctl[websocket]==0.7.1', 'wakeonlan==1.1.6']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Samsung TV Remote'
diff --git a/homeassistant/components/satel_integra/__init__.py b/homeassistant/components/satel_integra/__init__.py
index 5f5c43d961f..ea1029e4fe0 100644
--- a/homeassistant/components/satel_integra/__init__.py
+++ b/homeassistant/components/satel_integra/__init__.py
@@ -1,16 +1,15 @@
"""Support for Satel Integra devices."""
+import collections
import logging
import voluptuous as vol
-from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['satel_integra==0.3.2']
-
DEFAULT_ALARM_NAME = 'satel_integra'
DEFAULT_PORT = 7094
DEFAULT_CONF_ARM_HOME_MODE = 1
@@ -23,13 +22,14 @@ DOMAIN = 'satel_integra'
DATA_SATEL = 'satel_integra'
-CONF_DEVICE_PORT = 'port'
-CONF_DEVICE_PARTITION = 'partition'
+CONF_DEVICE_CODE = 'code'
+CONF_DEVICE_PARTITIONS = 'partitions'
CONF_ARM_HOME_MODE = 'arm_home_mode'
CONF_ZONE_NAME = 'name'
CONF_ZONE_TYPE = 'type'
CONF_ZONES = 'zones'
CONF_OUTPUTS = 'outputs'
+CONF_SWITCHABLE_OUTPUTS = 'switchable_outputs'
ZONES = 'zones'
@@ -44,20 +44,38 @@ SIGNAL_OUTPUTS_UPDATED = 'satel_integra.outputs_updated'
ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONE_NAME): cv.string,
vol.Optional(CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE): cv.string})
+EDITABLE_OUTPUT_SCHEMA = vol.Schema({vol.Required(CONF_ZONE_NAME): cv.string})
+PARTITION_SCHEMA = vol.Schema(
+ {vol.Required(CONF_ZONE_NAME): cv.string,
+ vol.Optional(CONF_ARM_HOME_MODE, default=DEFAULT_CONF_ARM_HOME_MODE):
+ vol.In([1, 2, 3]),
+ }
+ )
+
+
+def is_alarm_code_necessary(value):
+ """Check if alarm code must be configured."""
+ if value.get(CONF_SWITCHABLE_OUTPUTS) and CONF_DEVICE_CODE not in value:
+ raise vol.Invalid('You need to specify alarm '
+ ' code to use switchable_outputs')
+
+ return value
+
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
+ DOMAIN: vol.All({
vol.Required(CONF_HOST): cv.string,
- vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_PORT): cv.port,
- vol.Optional(CONF_DEVICE_PARTITION,
- default=DEFAULT_DEVICE_PARTITION): cv.positive_int,
- vol.Optional(CONF_ARM_HOME_MODE,
- default=DEFAULT_CONF_ARM_HOME_MODE): vol.In([1, 2, 3]),
+ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
+ vol.Optional(CONF_DEVICE_CODE): cv.string,
+ vol.Optional(CONF_DEVICE_PARTITIONS,
+ default={}): {vol.Coerce(int): PARTITION_SCHEMA},
vol.Optional(CONF_ZONES,
default={}): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_OUTPUTS,
default={}): {vol.Coerce(int): ZONE_SCHEMA},
- }),
+ vol.Optional(CONF_SWITCHABLE_OUTPUTS,
+ default={}): {vol.Coerce(int): EDITABLE_OUTPUT_SCHEMA},
+ }, is_alarm_code_necessary),
}, extra=vol.ALLOW_EXTRA)
@@ -67,13 +85,20 @@ async def async_setup(hass, config):
zones = conf.get(CONF_ZONES)
outputs = conf.get(CONF_OUTPUTS)
+ switchable_outputs = conf.get(CONF_SWITCHABLE_OUTPUTS)
host = conf.get(CONF_HOST)
- port = conf.get(CONF_DEVICE_PORT)
- partition = conf.get(CONF_DEVICE_PARTITION)
+ port = conf.get(CONF_PORT)
+ partitions = conf.get(CONF_DEVICE_PARTITIONS)
from satel_integra.satel_integra import AsyncSatel
- controller = AsyncSatel(host, port, hass.loop, zones, outputs, partition)
+ monitored_outputs = collections.OrderedDict(
+ list(outputs.items()) +
+ list(switchable_outputs.items())
+ )
+
+ controller = AsyncSatel(host, port, hass.loop,
+ zones, monitored_outputs, partitions)
hass.data[DATA_SATEL] = controller
@@ -96,7 +121,15 @@ async def async_setup(hass, config):
hass.async_create_task(
async_load_platform(hass, 'binary_sensor', DOMAIN,
- {CONF_ZONES: zones, CONF_OUTPUTS: outputs}, config)
+ {CONF_ZONES: zones,
+ CONF_OUTPUTS: outputs}, config)
+ )
+
+ hass.async_create_task(
+ async_load_platform(hass, 'switch', DOMAIN,
+ {CONF_SWITCHABLE_OUTPUTS: switchable_outputs,
+ CONF_DEVICE_CODE: conf.get(CONF_DEVICE_CODE)},
+ config)
)
@callback
diff --git a/homeassistant/components/satel_integra/alarm_control_panel.py b/homeassistant/components/satel_integra/alarm_control_panel.py
index d2d9f473051..a896d7e8061 100644
--- a/homeassistant/components/satel_integra/alarm_control_panel.py
+++ b/homeassistant/components/satel_integra/alarm_control_panel.py
@@ -11,13 +11,11 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import (
- CONF_ARM_HOME_MODE, CONF_DEVICE_PARTITION, DATA_SATEL,
+ CONF_ARM_HOME_MODE, CONF_DEVICE_PARTITIONS, DATA_SATEL, CONF_ZONE_NAME,
SIGNAL_PANEL_MESSAGE)
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['satel_integra']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@@ -25,23 +23,34 @@ async def async_setup_platform(
if not discovery_info:
return
- device = SatelIntegraAlarmPanel(
- "Alarm Panel",
- discovery_info.get(CONF_ARM_HOME_MODE),
- discovery_info.get(CONF_DEVICE_PARTITION))
+ configured_partitions = discovery_info[CONF_DEVICE_PARTITIONS]
+ controller = hass.data[DATA_SATEL]
- async_add_entities([device])
+ devices = []
+
+ for partition_num, device_config_data in configured_partitions.items():
+ zone_name = device_config_data[CONF_ZONE_NAME]
+ arm_home_mode = device_config_data.get(CONF_ARM_HOME_MODE)
+ device = SatelIntegraAlarmPanel(
+ controller,
+ zone_name,
+ arm_home_mode,
+ partition_num)
+ devices.append(device)
+
+ async_add_entities(devices)
class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an AlarmDecoder-based alarm panel."""
- def __init__(self, name, arm_home_mode, partition_id):
+ def __init__(self, controller, name, arm_home_mode, partition_id):
"""Initialize the alarm panel."""
self._name = name
self._state = None
self._arm_home_mode = arm_home_mode
self._partition_id = partition_id
+ self._satel = controller
async def async_added_to_hass(self):
"""Update alarm status and register callbacks for future updates."""
@@ -68,13 +77,13 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
# Default - disarmed:
hass_alarm_status = STATE_ALARM_DISARMED
- satel_controller = self.hass.data[DATA_SATEL]
- if not satel_controller.connected:
+ if not self._satel.connected:
return None
state_map = OrderedDict([
(AlarmState.TRIGGERED, STATE_ALARM_TRIGGERED),
(AlarmState.TRIGGERED_FIRE, STATE_ALARM_TRIGGERED),
+ (AlarmState.ENTRY_TIME, STATE_ALARM_PENDING),
(AlarmState.ARMED_MODE3, STATE_ALARM_ARMED_HOME),
(AlarmState.ARMED_MODE2, STATE_ALARM_ARMED_HOME),
(AlarmState.ARMED_MODE1, STATE_ALARM_ARMED_HOME),
@@ -82,13 +91,11 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
(AlarmState.EXIT_COUNTDOWN_OVER_10, STATE_ALARM_PENDING),
(AlarmState.EXIT_COUNTDOWN_UNDER_10, STATE_ALARM_PENDING)
])
- _LOGGER.debug("State map of Satel: %s",
- satel_controller.partition_states)
+ _LOGGER.debug("State map of Satel: %s", self._satel.partition_states)
for satel_state, ha_state in state_map.items():
- if satel_state in satel_controller.partition_states and\
- self._partition_id in\
- satel_controller.partition_states[satel_state]:
+ if satel_state in self._satel.partition_states and\
+ self._partition_id in self._satel.partition_states[satel_state]:
hass_alarm_status = ha_state
break
@@ -124,24 +131,24 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
_LOGGER.debug("Disarming, self._state: %s", self._state)
- await self.hass.data[DATA_SATEL].disarm(code)
+ await self._satel.disarm(code, [self._partition_id])
if clear_alarm_necessary:
# Wait 1s before clearing the alarm
await asyncio.sleep(1)
- await self.hass.data[DATA_SATEL].clear_alarm(code)
+ await self._satel.clear_alarm(code, [self._partition_id])
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
_LOGGER.debug("Arming away")
if code:
- await self.hass.data[DATA_SATEL].arm(code)
+ await self._satel.arm(code, [self._partition_id])
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
_LOGGER.debug("Arming home")
if code:
- await self.hass.data[DATA_SATEL].arm(
- code, self._arm_home_mode)
+ await self._satel.arm(
+ code, [self._partition_id], self._arm_home_mode)
diff --git a/homeassistant/components/satel_integra/binary_sensor.py b/homeassistant/components/satel_integra/binary_sensor.py
index 0384ff37f14..ebaf11f0766 100644
--- a/homeassistant/components/satel_integra/binary_sensor.py
+++ b/homeassistant/components/satel_integra/binary_sensor.py
@@ -6,10 +6,8 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import (
- CONF_OUTPUTS, CONF_ZONE_NAME, CONF_ZONE_TYPE, CONF_ZONES, DATA_SATEL,
- SIGNAL_OUTPUTS_UPDATED, SIGNAL_ZONES_UPDATED)
-
-DEPENDENCIES = ['satel_integra']
+ CONF_OUTPUTS, CONF_ZONE_NAME, CONF_ZONE_TYPE, CONF_ZONES,
+ SIGNAL_OUTPUTS_UPDATED, SIGNAL_ZONES_UPDATED, DATA_SATEL)
_LOGGER = logging.getLogger(__name__)
@@ -21,6 +19,7 @@ async def async_setup_platform(
return
configured_zones = discovery_info[CONF_ZONES]
+ controller = hass.data[DATA_SATEL]
devices = []
@@ -28,7 +27,7 @@ async def async_setup_platform(
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = SatelIntegraBinarySensor(
- zone_num, zone_name, zone_type, SIGNAL_ZONES_UPDATED)
+ controller, zone_num, zone_name, zone_type, SIGNAL_ZONES_UPDATED)
devices.append(device)
configured_outputs = discovery_info[CONF_OUTPUTS]
@@ -37,7 +36,7 @@ async def async_setup_platform(
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = SatelIntegraBinarySensor(
- zone_num, zone_name, zone_type, SIGNAL_OUTPUTS_UPDATED)
+ controller, zone_num, zone_name, zone_type, SIGNAL_OUTPUTS_UPDATED)
devices.append(device)
async_add_entities(devices)
@@ -46,25 +45,25 @@ async def async_setup_platform(
class SatelIntegraBinarySensor(BinarySensorDevice):
"""Representation of an Satel Integra binary sensor."""
- def __init__(self, device_number, device_name, zone_type, react_to_signal):
+ def __init__(self, controller, device_number, device_name,
+ zone_type, react_to_signal):
"""Initialize the binary_sensor."""
self._device_number = device_number
self._name = device_name
self._zone_type = zone_type
self._state = 0
self._react_to_signal = react_to_signal
+ self._satel = controller
async def async_added_to_hass(self):
"""Register callbacks."""
if self._react_to_signal == SIGNAL_OUTPUTS_UPDATED:
- if self._device_number in\
- self.hass.data[DATA_SATEL].violated_outputs:
+ if self._device_number in self._satel.violated_outputs:
self._state = 1
else:
self._state = 0
else:
- if self._device_number in\
- self.hass.data[DATA_SATEL].violated_zones:
+ if self._device_number in self._satel.violated_zones:
self._state = 1
else:
self._state = 0
diff --git a/homeassistant/components/satel_integra/manifest.json b/homeassistant/components/satel_integra/manifest.json
new file mode 100644
index 00000000000..ae56b54ce18
--- /dev/null
+++ b/homeassistant/components/satel_integra/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "satel_integra",
+ "name": "Satel integra",
+ "documentation": "https://www.home-assistant.io/components/satel_integra",
+ "requirements": [
+ "satel_integra==0.3.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/satel_integra/switch.py b/homeassistant/components/satel_integra/switch.py
new file mode 100644
index 00000000000..77c07569fa4
--- /dev/null
+++ b/homeassistant/components/satel_integra/switch.py
@@ -0,0 +1,97 @@
+"""Support for Satel Integra modifiable outputs represented as switches."""
+import logging
+
+from homeassistant.components.switch import SwitchDevice
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+
+from . import (
+ CONF_DEVICE_CODE, CONF_SWITCHABLE_OUTPUTS, CONF_ZONE_NAME,
+ SIGNAL_OUTPUTS_UPDATED, DATA_SATEL)
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['satel_integra']
+
+
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
+ """Set up the Satel Integra switch devices."""
+ if not discovery_info:
+ return
+
+ configured_zones = discovery_info[CONF_SWITCHABLE_OUTPUTS]
+ controller = hass.data[DATA_SATEL]
+
+ devices = []
+
+ for zone_num, device_config_data in configured_zones.items():
+ zone_name = device_config_data[CONF_ZONE_NAME]
+
+ device = SatelIntegraSwitch(
+ controller, zone_num, zone_name, discovery_info[CONF_DEVICE_CODE])
+ devices.append(device)
+
+ async_add_entities(devices)
+
+
+class SatelIntegraSwitch(SwitchDevice):
+ """Representation of an Satel switch."""
+
+ def __init__(self, controller, device_number, device_name, code):
+ """Initialize the binary_sensor."""
+ self._device_number = device_number
+ self._name = device_name
+ self._state = False
+ self._code = code
+ self._satel = controller
+
+ async def async_added_to_hass(self):
+ """Register callbacks."""
+ async_dispatcher_connect(
+ self.hass, SIGNAL_OUTPUTS_UPDATED, self._devices_updated)
+
+ @callback
+ def _devices_updated(self, zones):
+ """Update switch state, if needed."""
+ _LOGGER.debug("Update switch name: %s zones: %s", self._name, zones)
+ if self._device_number in zones:
+ new_state = self._read_state()
+ _LOGGER.debug("New state: %s", new_state)
+ if new_state != self._state:
+ self._state = new_state
+ self.async_schedule_update_ha_state()
+
+ async def async_turn_on(self, **kwargs):
+ """Turn the device on."""
+ _LOGGER.debug("Switch: %s status: %s,"
+ " turning on", self._name, self._state)
+ await self._satel.set_output(self._code, self._device_number, True)
+ self.async_schedule_update_ha_state()
+
+ async def async_turn_off(self, **kwargs):
+ """Turn the device off."""
+ _LOGGER.debug("Switch name: %s status: %s,"
+ " turning off", self._name, self._state)
+ await self._satel.set_output(self._code, self._device_number, False)
+ self.async_schedule_update_ha_state()
+
+ @property
+ def is_on(self):
+ """Return true if device is on."""
+ self._state = self._read_state()
+ return self._state
+
+ def _read_state(self):
+ """Read state of the device."""
+ return self._device_number in self._satel.violated_outputs
+
+ @property
+ def name(self):
+ """Return the name of the switch."""
+ return self._name
+
+ @property
+ def should_poll(self):
+ """Don't poll."""
+ return False
diff --git a/homeassistant/components/scene/manifest.json b/homeassistant/components/scene/manifest.json
new file mode 100644
index 00000000000..e1becfd1936
--- /dev/null
+++ b/homeassistant/components/scene/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "scene",
+ "name": "Scene",
+ "documentation": "https://www.home-assistant.io/components/scene",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/scrape/manifest.json b/homeassistant/components/scrape/manifest.json
new file mode 100644
index 00000000000..c7e60140dbf
--- /dev/null
+++ b/homeassistant/components/scrape/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "scrape",
+ "name": "Scrape",
+ "documentation": "https://www.home-assistant.io/components/scrape",
+ "requirements": [
+ "beautifulsoup4==4.7.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py
index a6d16852df3..a5975d4f9d0 100644
--- a/homeassistant/components/scrape/sensor.py
+++ b/homeassistant/components/scrape/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for getting data from websites with scraping.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.scrape/
-"""
+"""Support for getting data from websites with scraping."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['beautifulsoup4==4.7.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ATTR = 'attribute'
diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py
index 873a18120ac..528e454c4e6 100644
--- a/homeassistant/components/script/__init__.py
+++ b/homeassistant/components/script/__init__.py
@@ -18,8 +18,6 @@ from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'script'
-DEPENDENCIES = ['group']
-
ATTR_CAN_CANCEL = 'can_cancel'
ATTR_LAST_ACTION = 'last_action'
ATTR_LAST_TRIGGERED = 'last_triggered'
diff --git a/homeassistant/components/script/manifest.json b/homeassistant/components/script/manifest.json
new file mode 100644
index 00000000000..56a3c39b7b6
--- /dev/null
+++ b/homeassistant/components/script/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "script",
+ "name": "Script",
+ "documentation": "https://www.home-assistant.io/components/script",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/script/services.yaml b/homeassistant/components/script/services.yaml
new file mode 100644
index 00000000000..736b0ec71c3
--- /dev/null
+++ b/homeassistant/components/script/services.yaml
@@ -0,0 +1,25 @@
+# Describes the format for available python_script services
+
+reload:
+ description: Reload all the available scripts
+
+turn_on:
+ description: Turn on script
+ fields:
+ entity_id:
+ description: Name(s) of script to be turned on.
+ example: 'script.arrive_home'
+
+turn_off:
+ description: Turn off script
+ fields:
+ entity_id:
+ description: Name(s) of script to be turned off.
+ example: 'script.arrive_home'
+
+toggle:
+ description: Toggle script
+ fields:
+ entity_id:
+ description: Name(s) of script to be toggled.
+ example: 'script.arrive_home'
diff --git a/homeassistant/components/scsgate/__init__.py b/homeassistant/components/scsgate/__init__.py
index 79bf4e217c9..ed84bc19ebe 100644
--- a/homeassistant/components/scsgate/__init__.py
+++ b/homeassistant/components/scsgate/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for SCSGate components.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/scsgate/
-"""
+"""Support for SCSGate components."""
import logging
from threading import Lock
@@ -13,8 +8,6 @@ from homeassistant.const import (CONF_DEVICE, CONF_NAME)
from homeassistant.core import EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['scsgate==0.1.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_STATE = 'state'
diff --git a/homeassistant/components/scsgate/cover.py b/homeassistant/components/scsgate/cover.py
index fc1c16e1ff3..d866b8ab80c 100644
--- a/homeassistant/components/scsgate/cover.py
+++ b/homeassistant/components/scsgate/cover.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['scsgate']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES):
cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA),
diff --git a/homeassistant/components/scsgate/light.py b/homeassistant/components/scsgate/light.py
index 87d7e02b383..f894d33e867 100644
--- a/homeassistant/components/scsgate/light.py
+++ b/homeassistant/components/scsgate/light.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['scsgate']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES):
cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA),
diff --git a/homeassistant/components/scsgate/manifest.json b/homeassistant/components/scsgate/manifest.json
new file mode 100644
index 00000000000..d565a5d336d
--- /dev/null
+++ b/homeassistant/components/scsgate/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "scsgate",
+ "name": "Scsgate",
+ "documentation": "https://www.home-assistant.io/components/scsgate",
+ "requirements": [
+ "scsgate==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/scsgate/switch.py b/homeassistant/components/scsgate/switch.py
index 2b2bf2de94f..ad6362c9e0c 100644
--- a/homeassistant/components/scsgate/switch.py
+++ b/homeassistant/components/scsgate/switch.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
ATTR_SCENARIO_ID = 'scenario_id'
-DEPENDENCIES = ['scsgate']
-
CONF_TRADITIONAL = 'traditional'
CONF_SCENARIO = 'scenario'
diff --git a/homeassistant/components/season/.translations/sensor.nn.json b/homeassistant/components/season/.translations/sensor.nn.json
index dbcff7ef819..3e8f626c172 100644
--- a/homeassistant/components/season/.translations/sensor.nn.json
+++ b/homeassistant/components/season/.translations/sensor.nn.json
@@ -1,7 +1,5 @@
{
"state": {
- "autumn": "Haust",
- "spring": "V\u00e5r",
"summer": "Sommar",
"winter": "Vinter"
}
diff --git a/homeassistant/components/season/.translations/sensor.zh-Hans.json b/homeassistant/components/season/.translations/sensor.zh-Hans.json
index 78801f4b1df..e441b1aa8ac 100644
--- a/homeassistant/components/season/.translations/sensor.zh-Hans.json
+++ b/homeassistant/components/season/.translations/sensor.zh-Hans.json
@@ -1,8 +1,8 @@
{
"state": {
- "autumn": "\u79cb\u5b63",
- "spring": "\u6625\u5b63",
- "summer": "\u590f\u5b63",
- "winter": "\u51ac\u5b63"
+ "autumn": "\u79cb",
+ "spring": "\u6625",
+ "summer": "\u590f",
+ "winter": "\u51ac"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/season/manifest.json b/homeassistant/components/season/manifest.json
new file mode 100644
index 00000000000..74bdb3d8b88
--- /dev/null
+++ b/homeassistant/components/season/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "season",
+ "name": "Season",
+ "documentation": "https://www.home-assistant.io/components/season",
+ "requirements": [
+ "ephem==3.7.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py
index 84a2b426e9e..b31cf5083bf 100644
--- a/homeassistant/components/season/sensor.py
+++ b/homeassistant/components/season/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for tracking which astronomical or meteorological season it is.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor/season/
-"""
+"""Support for tracking which astronomical or meteorological season it is."""
import logging
from datetime import datetime
@@ -14,8 +9,6 @@ from homeassistant.const import CONF_TYPE
from homeassistant.helpers.entity import Entity
from homeassistant import util
-REQUIREMENTS = ['ephem==3.7.6.0']
-
_LOGGER = logging.getLogger(__name__)
NORTHERN = 'northern'
diff --git a/homeassistant/components/sendgrid/manifest.json b/homeassistant/components/sendgrid/manifest.json
new file mode 100644
index 00000000000..47372d861f1
--- /dev/null
+++ b/homeassistant/components/sendgrid/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sendgrid",
+ "name": "Sendgrid",
+ "documentation": "https://www.home-assistant.io/components/sendgrid",
+ "requirements": [
+ "sendgrid==5.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sendgrid/notify.py b/homeassistant/components/sendgrid/notify.py
index 211e288725e..563e47b8afe 100644
--- a/homeassistant/components/sendgrid/notify.py
+++ b/homeassistant/components/sendgrid/notify.py
@@ -1,9 +1,4 @@
-"""
-SendGrid notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.sendgrid/
-"""
+"""SendGrid notification service."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService)
-REQUIREMENTS = ['sendgrid==5.6.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_SENDER_NAME = 'sender_name'
diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py
index be3ab75b555..7266b2fb1e5 100644
--- a/homeassistant/components/sense/__init__.py
+++ b/homeassistant/components/sense/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ['sense_energy==0.7.0']
-
_LOGGER = logging.getLogger(__name__)
ACTIVE_UPDATE_RATE = 60
diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py
index da9bae3cc84..a0f65ac555a 100644
--- a/homeassistant/components/sense/binary_sensor.py
+++ b/homeassistant/components/sense/binary_sensor.py
@@ -5,8 +5,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import SENSE_DATA
-DEPENDENCIES = ['sense']
-
_LOGGER = logging.getLogger(__name__)
BIN_SENSOR_CLASS = 'power'
diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json
new file mode 100644
index 00000000000..272a4a58f33
--- /dev/null
+++ b/homeassistant/components/sense/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sense",
+ "name": "Sense",
+ "documentation": "https://www.home-assistant.io/components/sense",
+ "requirements": [
+ "sense_energy==0.7.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py
index 0224884e18a..e114132e0d7 100644
--- a/homeassistant/components/sense/sensor.py
+++ b/homeassistant/components/sense/sensor.py
@@ -15,8 +15,6 @@ ACTIVE_TYPE = 'active'
CONSUMPTION_NAME = 'Usage'
-DEPENDENCIES = ['sense']
-
ICON = 'mdi:flash'
MIN_TIME_BETWEEN_DAILY_UPDATES = timedelta(seconds=300)
diff --git a/homeassistant/components/sensehat/light.py b/homeassistant/components/sensehat/light.py
index 86153fffef8..713dd1373fd 100644
--- a/homeassistant/components/sensehat/light.py
+++ b/homeassistant/components/sensehat/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Sense Hat LEDs.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.sensehat/
-"""
+"""Support for Sense Hat LEDs."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.light import (
from homeassistant.const import CONF_NAME
import homeassistant.util.color as color_util
-REQUIREMENTS = ['sense-hat==2.2.0']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_SENSEHAT = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
diff --git a/homeassistant/components/sensehat/manifest.json b/homeassistant/components/sensehat/manifest.json
new file mode 100644
index 00000000000..cb148c92198
--- /dev/null
+++ b/homeassistant/components/sensehat/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sensehat",
+ "name": "Sensehat",
+ "documentation": "https://www.home-assistant.io/components/sensehat",
+ "requirements": [
+ "sense-hat==2.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sensehat/sensor.py b/homeassistant/components/sensehat/sensor.py
index 15c73d990e1..a9a80ee22ba 100644
--- a/homeassistant/components/sensehat/sensor.py
+++ b/homeassistant/components/sensehat/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Sense HAT sensors.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.sensehat
-"""
+"""Support for Sense HAT sensors."""
import os
import logging
from datetime import timedelta
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['sense-hat==2.2.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'sensehat'
diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py
index 3affaba3e1f..d2f95dcee79 100644
--- a/homeassistant/components/sensibo/climate.py
+++ b/homeassistant/components/sensibo/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for Sensibo wifi-enabled home thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.sensibo/
-"""
+"""Support for Sensibo wifi-enabled home thermostats."""
import asyncio
import logging
@@ -26,8 +21,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.temperature import convert as convert_temperature
-REQUIREMENTS = ['pysensibo==1.0.3']
-
_LOGGER = logging.getLogger(__name__)
ALL = ['all']
diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json
new file mode 100644
index 00000000000..776b8444b82
--- /dev/null
+++ b/homeassistant/components/sensibo/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "sensibo",
+ "name": "Sensibo",
+ "documentation": "https://www.home-assistant.io/components/sensibo",
+ "requirements": [
+ "pysensibo==1.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@andrey-git"
+ ]
+}
diff --git a/homeassistant/components/sensibo/services.yaml b/homeassistant/components/sensibo/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/sensor/.translations/moon.es.json b/homeassistant/components/sensor/.translations/moon.es.json
index 3ce14cd4c77..bf8cacca21c 100644
--- a/homeassistant/components/sensor/.translations/moon.es.json
+++ b/homeassistant/components/sensor/.translations/moon.es.json
@@ -5,6 +5,8 @@
"last_quarter": "\u00daltimo cuarto",
"new_moon": "Luna nueva",
"waning_crescent": "Luna menguante",
- "waxing_crescent": "Luna creciente"
+ "waning_gibbous": "Gibosa menguante",
+ "waxing_crescent": "Luna creciente",
+ "waxing_gibbous": "Gibosa creciente"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/sensor/.translations/moon.th.json b/homeassistant/components/sensor/.translations/moon.th.json
new file mode 100644
index 00000000000..5d65c23226d
--- /dev/null
+++ b/homeassistant/components/sensor/.translations/moon.th.json
@@ -0,0 +1,5 @@
+{
+ "state": {
+ "full_moon": "\u0e1e\u0e23\u0e30\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c\u0e40\u0e15\u0e47\u0e21\u0e14\u0e27\u0e07"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py
index 50549f28fd7..89963118300 100644
--- a/homeassistant/components/sensor/__init__.py
+++ b/homeassistant/components/sensor/__init__.py
@@ -1,21 +1,17 @@
-"""
-Component to interface with various sensors that can be monitored.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor/
-"""
+"""Component to interface with various sensors that can be monitored."""
from datetime import timedelta
import logging
import voluptuous as vol
-from homeassistant.helpers.entity_component import EntityComponent
-from homeassistant.helpers.config_validation import ( # noqa
- PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
from homeassistant.const import (
DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE,
- DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_PRESSURE)
+ DEVICE_CLASS_POWER, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_SIGNAL_STRENGTH,
+ DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP)
+from homeassistant.helpers.config_validation import ( # noqa
+ PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
+from homeassistant.helpers.entity_component import EntityComponent
_LOGGER = logging.getLogger(__name__)
@@ -28,9 +24,11 @@ DEVICE_CLASSES = [
DEVICE_CLASS_BATTERY, # % of battery that is left
DEVICE_CLASS_HUMIDITY, # % of humidity in the air
DEVICE_CLASS_ILLUMINANCE, # current light level (lx/lm)
+ DEVICE_CLASS_SIGNAL_STRENGTH, # signal strength (dB/dBm)
DEVICE_CLASS_TEMPERATURE, # temperature (C/F)
DEVICE_CLASS_TIMESTAMP, # timestamp (ISO8601)
DEVICE_CLASS_PRESSURE, # pressure (hPa/mbar)
+ DEVICE_CLASS_POWER, # power (W/kW)
]
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
diff --git a/homeassistant/components/sensor/manifest.json b/homeassistant/components/sensor/manifest.json
new file mode 100644
index 00000000000..813bcc27aca
--- /dev/null
+++ b/homeassistant/components/sensor/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "sensor",
+ "name": "Sensor",
+ "documentation": "https://www.home-assistant.io/components/sensor",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/serial/manifest.json b/homeassistant/components/serial/manifest.json
new file mode 100644
index 00000000000..945464dbdec
--- /dev/null
+++ b/homeassistant/components/serial/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "serial",
+ "name": "Serial",
+ "documentation": "https://www.home-assistant.io/components/serial",
+ "requirements": [
+ "pyserial-asyncio==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py
index 5d49b065558..cebe9b42996 100644
--- a/homeassistant/components/serial/sensor.py
+++ b/homeassistant/components/serial/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for reading data from a serial port.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.serial/
-"""
+"""Support for reading data from a serial port."""
import logging
import json
@@ -15,8 +10,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyserial-asyncio==0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_SERIAL_PORT = 'serial_port'
diff --git a/homeassistant/components/serial_pm/manifest.json b/homeassistant/components/serial_pm/manifest.json
new file mode 100644
index 00000000000..b2a645c88f3
--- /dev/null
+++ b/homeassistant/components/serial_pm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "serial_pm",
+ "name": "Serial pm",
+ "documentation": "https://www.home-assistant.io/components/serial_pm",
+ "requirements": [
+ "pmsensor==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/serial_pm/sensor.py b/homeassistant/components/serial_pm/sensor.py
index 46dfc9fae75..49e61bead05 100644
--- a/homeassistant/components/serial_pm/sensor.py
+++ b/homeassistant/components/serial_pm/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for particulate matter sensors connected to a serial port.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.serial_pm/
-"""
+"""Support for particulate matter sensors connected to a serial port."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
-REQUIREMENTS = ['pmsensor==0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_SERIAL_DEVICE = 'serial_device'
diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml
deleted file mode 100644
index 40c76376531..00000000000
--- a/homeassistant/components/services.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-# Describes the format for available component services
-
-homeassistant:
- check_config:
- description: Check the Home Assistant configuration files for errors. Errors will be displayed in the Home Assistant log.
- reload_core_config:
- description: Reload the core configuration.
- restart:
- description: Restart the Home Assistant service.
- stop:
- description: Stop the Home Assistant service.
- toggle:
- description: Generic service to toggle devices on/off under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services.
- fields:
- entity_id:
- description: The entity_id of the device to toggle on/off.
- example: light.living_room
- turn_on:
- description: Generic service to turn devices on under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services.
- fields:
- entity_id:
- description: The entity_id of the device to turn on.
- example: light.living_room
- turn_off:
- description: Generic service to turn devices off under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services.
- fields:
- entity_id:
- description: The entity_id of the device to turn off.
- example: light.living_room
- update_entity:
- description: Force one or more entities to update its data
- fields:
- entity_id:
- description: One or multiple entity_ids to update. Can be a list.
- example: light.living_room
diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py
index 44a6cfb265c..3882d8796c7 100644
--- a/homeassistant/components/sesame/lock.py
+++ b/homeassistant/components/sesame/lock.py
@@ -1,9 +1,4 @@
-"""
-Support for Sesame, by CANDY HOUSE.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/lock.sesame/
-"""
+"""Support for Sesame, by CANDY HOUSE."""
from typing import Callable
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED)
from homeassistant.helpers.typing import ConfigType
-REQUIREMENTS = ['pysesame==0.1.0']
-
ATTR_DEVICE_ID = 'device_id'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/sesame/manifest.json b/homeassistant/components/sesame/manifest.json
new file mode 100644
index 00000000000..9aed47462fe
--- /dev/null
+++ b/homeassistant/components/sesame/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sesame",
+ "name": "Sesame",
+ "documentation": "https://www.home-assistant.io/components/sesame",
+ "requirements": [
+ "pysesame==0.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/seven_segments/image_processing.py b/homeassistant/components/seven_segments/image_processing.py
index a460115cc34..7bbfceb15e4 100644
--- a/homeassistant/components/seven_segments/image_processing.py
+++ b/homeassistant/components/seven_segments/image_processing.py
@@ -1,9 +1,4 @@
-"""
-Local optical character recognition processing of seven segments displays.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/image_processing.seven_segments/
-"""
+"""Optical character recognition processing of seven segments displays."""
import logging
import io
import os
diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json
new file mode 100644
index 00000000000..45ce2f6a7a0
--- /dev/null
+++ b/homeassistant/components/seven_segments/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "seven_segments",
+ "name": "Seven segments",
+ "documentation": "https://www.home-assistant.io/components/seven_segments",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/seventeentrack/manifest.json b/homeassistant/components/seventeentrack/manifest.json
new file mode 100644
index 00000000000..47b36c12291
--- /dev/null
+++ b/homeassistant/components/seventeentrack/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "seventeentrack",
+ "name": "Seventeentrack",
+ "documentation": "https://www.home-assistant.io/components/seventeentrack",
+ "requirements": [
+ "py17track==2.2.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py
index 6fb4884989b..f9bae50698b 100644
--- a/homeassistant/components/seventeentrack/sensor.py
+++ b/homeassistant/components/seventeentrack/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for package tracking sensors from 17track.net.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.seventeentrack/
-"""
+"""Support for package tracking sensors from 17track.net."""
import logging
from datetime import timedelta
@@ -17,7 +12,6 @@ from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle, slugify
-REQUIREMENTS = ['py17track==2.2.2']
_LOGGER = logging.getLogger(__name__)
ATTR_DESTINATION_COUNTRY = 'destination_country'
@@ -237,7 +231,8 @@ class SeventeenTrackPackageSensor(Entity):
return
# If the user has elected to not see delivered packages and one gets
- # delivered, post a notification and delete the entity:
+ # delivered, post a notification, remove the entity from the UI, and
+ # delete it from the entity registry:
if package.status == VALUE_DELIVERED and not self._data.show_delivered:
_LOGGER.info('Package delivered: %s', self._tracking_number)
self.hass.components.persistent_notification.create(
@@ -250,6 +245,9 @@ class SeventeenTrackPackageSensor(Entity):
title=NOTIFICATION_DELIVERED_TITLE,
notification_id=NOTIFICATION_DELIVERED_ID_SCAFFOLD.format(
self._tracking_number))
+
+ reg = self.hass.helpers.entity_registry.async_get_registry()
+ self.hass.async_create_task(reg.async_remove(self.entity_id))
self.hass.async_create_task(self.async_remove())
return
diff --git a/homeassistant/components/shell_command/manifest.json b/homeassistant/components/shell_command/manifest.json
new file mode 100644
index 00000000000..dfe9a8e8e6f
--- /dev/null
+++ b/homeassistant/components/shell_command/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "shell_command",
+ "name": "Shell command",
+ "documentation": "https://www.home-assistant.io/components/shell_command",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/shell_command/services.yaml b/homeassistant/components/shell_command/services.yaml
new file mode 100644
index 00000000000..df056f94e85
--- /dev/null
+++ b/homeassistant/components/shell_command/services.yaml
@@ -0,0 +1 @@
+# Empty file, shell_command services are dynamically created
diff --git a/homeassistant/components/shiftr/__init__.py b/homeassistant/components/shiftr/__init__.py
index 438bc36b1bf..df25365a9fb 100644
--- a/homeassistant/components/shiftr/__init__.py
+++ b/homeassistant/components/shiftr/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import state as state_helper
-REQUIREMENTS = ['paho-mqtt==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'shiftr'
diff --git a/homeassistant/components/shiftr/manifest.json b/homeassistant/components/shiftr/manifest.json
new file mode 100644
index 00000000000..02718396e5e
--- /dev/null
+++ b/homeassistant/components/shiftr/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "shiftr",
+ "name": "Shiftr",
+ "documentation": "https://www.home-assistant.io/components/shiftr",
+ "requirements": [
+ "paho-mqtt==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/shodan/manifest.json b/homeassistant/components/shodan/manifest.json
new file mode 100644
index 00000000000..8898b7976b5
--- /dev/null
+++ b/homeassistant/components/shodan/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "shodan",
+ "name": "Shodan",
+ "documentation": "https://www.home-assistant.io/components/shodan",
+ "requirements": [
+ "shodan==1.11.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py
index ee64eecf3fe..072742391b9 100644
--- a/homeassistant/components/shodan/sensor.py
+++ b/homeassistant/components/shodan/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['shodan==1.11.1']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Shodan"
diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py
index 1a036f3661a..cfcbfdd4224 100644
--- a/homeassistant/components/shopping_list/__init__.py
+++ b/homeassistant/components/shopping_list/__init__.py
@@ -18,7 +18,6 @@ from homeassistant.components import websocket_api
ATTR_NAME = 'name'
DOMAIN = 'shopping_list'
-DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
EVENT = 'shopping_list_updated'
diff --git a/homeassistant/components/shopping_list/manifest.json b/homeassistant/components/shopping_list/manifest.json
new file mode 100644
index 00000000000..b4ea3c2d388
--- /dev/null
+++ b/homeassistant/components/shopping_list/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "shopping_list",
+ "name": "Shopping list",
+ "documentation": "https://www.home-assistant.io/components/shopping_list",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sht31/manifest.json b/homeassistant/components/sht31/manifest.json
new file mode 100644
index 00000000000..dfa22fc6e23
--- /dev/null
+++ b/homeassistant/components/sht31/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "sht31",
+ "name": "Sht31",
+ "documentation": "https://www.home-assistant.io/components/sht31",
+ "requirements": [
+ "Adafruit-GPIO==1.0.3",
+ "Adafruit-SHT31==1.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sht31/sensor.py b/homeassistant/components/sht31/sensor.py
index 4b849849771..904a1af75c2 100644
--- a/homeassistant/components/sht31/sensor.py
+++ b/homeassistant/components/sht31/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Sensirion SHT31 temperature and humidity sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.sht31/
-"""
+"""Support for Sensirion SHT31 temperature and humidity sensor."""
from datetime import timedelta
import logging
@@ -21,9 +16,6 @@ from homeassistant.const import PRECISION_TENTHS
from homeassistant.util import Throttle
-REQUIREMENTS = ['Adafruit-GPIO==1.0.3',
- 'Adafruit-SHT31==1.0.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_I2C_ADDRESS = 'i2c_address'
diff --git a/homeassistant/components/sigfox/manifest.json b/homeassistant/components/sigfox/manifest.json
new file mode 100644
index 00000000000..1dc8f5255ce
--- /dev/null
+++ b/homeassistant/components/sigfox/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "sigfox",
+ "name": "Sigfox",
+ "documentation": "https://www.home-assistant.io/components/sigfox",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py
index 5e2a56cadc3..1bce2d6b28d 100644
--- a/homeassistant/components/sigfox/sensor.py
+++ b/homeassistant/components/sigfox/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for SigFox devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.sigfox/
-"""
+"""Sensor for SigFox devices."""
import logging
import datetime
import json
diff --git a/homeassistant/components/simplepush/manifest.json b/homeassistant/components/simplepush/manifest.json
new file mode 100644
index 00000000000..cbf2833a4f7
--- /dev/null
+++ b/homeassistant/components/simplepush/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "simplepush",
+ "name": "Simplepush",
+ "documentation": "https://www.home-assistant.io/components/simplepush",
+ "requirements": [
+ "simplepush==1.1.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/simplepush/notify.py b/homeassistant/components/simplepush/notify.py
index 63222d4adc1..38a61e43972 100644
--- a/homeassistant/components/simplepush/notify.py
+++ b/homeassistant/components/simplepush/notify.py
@@ -1,9 +1,4 @@
-"""
-Simplepush notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.simplepush/
-"""
+"""Simplepush notification service."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService)
-REQUIREMENTS = ['simplepush==1.1.4']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ENCRYPTED = 'encrypted'
diff --git a/homeassistant/components/simplisafe/.translations/ko.json b/homeassistant/components/simplisafe/.translations/ko.json
index 8fb056e3f93..5cbe233a05e 100644
--- a/homeassistant/components/simplisafe/.translations/ko.json
+++ b/homeassistant/components/simplisafe/.translations/ko.json
@@ -11,7 +11,7 @@
"password": "\ube44\ubc00\ubc88\ud638",
"username": "\uc774\uba54\uc77c \uc8fc\uc18c"
},
- "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694"
+ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825"
}
},
"title": "SimpliSafe"
diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py
index e6b9aba643d..36aa3ba54d9 100644
--- a/homeassistant/components/simplisafe/__init__.py
+++ b/homeassistant/components/simplisafe/__init__.py
@@ -1,4 +1,5 @@
"""Support for SimpliSafe alarm systems."""
+import asyncio
import logging
from datetime import timedelta
@@ -18,8 +19,6 @@ from homeassistant.helpers import config_validation as cv
from .config_flow import configured_instances
from .const import DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN, TOPIC_UPDATE
-REQUIREMENTS = ['simplisafe-python==3.4.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ACCOUNTS = 'accounts'
@@ -107,17 +106,18 @@ async def async_setup_entry(hass, config_entry):
async def refresh(event_time):
"""Refresh data from the SimpliSafe account."""
- for system in systems:
- _LOGGER.debug('Updating system data: %s', system.system_id)
-
- try:
- await system.update()
- except SimplipyError as err:
+ tasks = [system.update() for system in systems]
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+ for system, result in zip(systems, results):
+ if isinstance(result, SimplipyError):
_LOGGER.error(
- 'There was error updating "%s": %s', system.address, err)
+ 'There was error updating "%s": %s', system.address,
+ result)
continue
- async_dispatcher_send(hass, TOPIC_UPDATE.format(system.system_id))
+ _LOGGER.debug('Updated status of "%s"', system.address)
+ async_dispatcher_send(
+ hass, TOPIC_UPDATE.format(system.system_id))
if system.api.refresh_token_dirty:
_async_save_refresh_token(
diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json
new file mode 100644
index 00000000000..eac586b355d
--- /dev/null
+++ b/homeassistant/components/simplisafe/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "simplisafe",
+ "name": "Simplisafe",
+ "documentation": "https://www.home-assistant.io/components/simplisafe",
+ "requirements": [
+ "simplisafe-python==3.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/simulated/manifest.json b/homeassistant/components/simulated/manifest.json
new file mode 100644
index 00000000000..b972152aea4
--- /dev/null
+++ b/homeassistant/components/simulated/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "simulated",
+ "name": "Simulated",
+ "documentation": "https://www.home-assistant.io/components/simulated",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py
index 8f2c3dd36e0..562f355f76b 100644
--- a/homeassistant/components/simulated/sensor.py
+++ b/homeassistant/components/simulated/sensor.py
@@ -1,9 +1,4 @@
-"""
-Adds a simulated sensor.
-
-For more details about this platform, refer to the documentation at
-https://home-assistant.io/components/sensor.simulated/
-"""
+"""Adds a simulated sensor."""
import logging
import math
from random import Random
diff --git a/homeassistant/components/sisyphus/__init__.py b/homeassistant/components/sisyphus/__init__.py
index b1bec15d40e..7cc8e3efd33 100644
--- a/homeassistant/components/sisyphus/__init__.py
+++ b/homeassistant/components/sisyphus/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
-REQUIREMENTS = ['sisyphus-control==2.1']
-
_LOGGER = logging.getLogger(__name__)
DATA_SISYPHUS = 'sisyphus'
diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py
index 182e5e78198..8d882925796 100644
--- a/homeassistant/components/sisyphus/light.py
+++ b/homeassistant/components/sisyphus/light.py
@@ -8,8 +8,6 @@ from . import DATA_SISYPHUS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['sisyphus']
-
SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS
diff --git a/homeassistant/components/sisyphus/manifest.json b/homeassistant/components/sisyphus/manifest.json
new file mode 100644
index 00000000000..b1809e7a572
--- /dev/null
+++ b/homeassistant/components/sisyphus/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sisyphus",
+ "name": "Sisyphus",
+ "documentation": "https://www.home-assistant.io/components/sisyphus",
+ "requirements": [
+ "sisyphus-control==2.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py
index 11546c3fd43..65f5cb48e59 100644
--- a/homeassistant/components/sisyphus/media_player.py
+++ b/homeassistant/components/sisyphus/media_player.py
@@ -13,8 +13,6 @@ from . import DATA_SISYPHUS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['sisyphus']
-
MEDIA_TYPE_TRACK = 'sisyphus_track'
SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE \
diff --git a/homeassistant/components/sky_hub/device_tracker.py b/homeassistant/components/sky_hub/device_tracker.py
index 0d69e08aa71..4e0ce4352cc 100644
--- a/homeassistant/components/sky_hub/device_tracker.py
+++ b/homeassistant/components/sky_hub/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Sky Hub.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.sky_hub/
-"""
+"""Support for Sky Hub."""
import logging
import re
diff --git a/homeassistant/components/sky_hub/manifest.json b/homeassistant/components/sky_hub/manifest.json
new file mode 100644
index 00000000000..46337918f84
--- /dev/null
+++ b/homeassistant/components/sky_hub/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "sky_hub",
+ "name": "Sky hub",
+ "documentation": "https://www.home-assistant.io/components/sky_hub",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/skybeacon/manifest.json b/homeassistant/components/skybeacon/manifest.json
new file mode 100644
index 00000000000..893a1f3469e
--- /dev/null
+++ b/homeassistant/components/skybeacon/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "skybeacon",
+ "name": "Skybeacon",
+ "documentation": "https://www.home-assistant.io/components/skybeacon",
+ "requirements": [
+ "pygatt[GATTTOOL]==4.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py
index 6960999306d..aa7eea0bccc 100644
--- a/homeassistant/components/skybeacon/sensor.py
+++ b/homeassistant/components/skybeacon/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Skybeacon temperature/humidity Bluetooth LE sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.skybeacon/
-"""
+"""Support for Skybeacon temperature/humidity Bluetooth LE sensors."""
import logging
import threading
from uuid import UUID
@@ -16,8 +11,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pygatt[GATTTOOL]==3.2.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DEVICE = 'device'
diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py
index 31d1339fbcf..51e12376a61 100644
--- a/homeassistant/components/skybell/__init__.py
+++ b/homeassistant/components/skybell/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['skybellpy==0.3.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Skybell.com"
diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py
index 8c2b8355258..16b094dbf63 100644
--- a/homeassistant/components/skybell/binary_sensor.py
+++ b/homeassistant/components/skybell/binary_sensor.py
@@ -12,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice
-DEPENDENCIES = ['skybell']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
diff --git a/homeassistant/components/skybell/camera.py b/homeassistant/components/skybell/camera.py
index 04b03f84bf7..e11f7c48947 100644
--- a/homeassistant/components/skybell/camera.py
+++ b/homeassistant/components/skybell/camera.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN as SKYBELL_DOMAIN, SkybellDevice
-DEPENDENCIES = ['skybell']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
diff --git a/homeassistant/components/skybell/light.py b/homeassistant/components/skybell/light.py
index d413f9df412..18108787e56 100644
--- a/homeassistant/components/skybell/light.py
+++ b/homeassistant/components/skybell/light.py
@@ -7,8 +7,6 @@ import homeassistant.util.color as color_util
from . import DOMAIN as SKYBELL_DOMAIN, SkybellDevice
-DEPENDENCIES = ['skybell']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/skybell/manifest.json b/homeassistant/components/skybell/manifest.json
new file mode 100644
index 00000000000..843fd3d13b0
--- /dev/null
+++ b/homeassistant/components/skybell/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "skybell",
+ "name": "Skybell",
+ "documentation": "https://www.home-assistant.io/components/skybell",
+ "requirements": [
+ "skybellpy==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py
index 067e850dfcf..5188f75b6fa 100644
--- a/homeassistant/components/skybell/sensor.py
+++ b/homeassistant/components/skybell/sensor.py
@@ -11,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice
-DEPENDENCIES = ['skybell']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=30)
diff --git a/homeassistant/components/skybell/switch.py b/homeassistant/components/skybell/switch.py
index 674bbf22a08..7771f1a8754 100644
--- a/homeassistant/components/skybell/switch.py
+++ b/homeassistant/components/skybell/switch.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice
-DEPENDENCIES = ['skybell']
-
_LOGGER = logging.getLogger(__name__)
# Switch types: Name
diff --git a/homeassistant/components/slack/manifest.json b/homeassistant/components/slack/manifest.json
new file mode 100644
index 00000000000..3b6e764f814
--- /dev/null
+++ b/homeassistant/components/slack/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "slack",
+ "name": "Slack",
+ "documentation": "https://www.home-assistant.io/components/slack",
+ "requirements": [
+ "slacker==0.12.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py
index eabddf01299..600fdef9477 100644
--- a/homeassistant/components/slack/notify.py
+++ b/homeassistant/components/slack/notify.py
@@ -1,9 +1,4 @@
-"""
-Slack platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.slack/
-"""
+"""Slack platform for notify component."""
import logging
import requests
@@ -17,8 +12,6 @@ from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['slacker==0.12.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_CHANNEL = 'default_channel'
diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py
index 7a23c6c4609..97b2d53a033 100644
--- a/homeassistant/components/sleepiq/__init__.py
+++ b/homeassistant/components/sleepiq/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.util import Throttle
DOMAIN = 'sleepiq'
-REQUIREMENTS = ['sleepyq==0.6']
-
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
IS_IN_BED = 'is_in_bed'
diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py
index 808eda4967d..cad5c9e42f1 100644
--- a/homeassistant/components/sleepiq/binary_sensor.py
+++ b/homeassistant/components/sleepiq/binary_sensor.py
@@ -1,14 +1,7 @@
-"""
-Support for SleepIQ sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.sleepiq/
-"""
+"""Support for SleepIQ sensors."""
from homeassistant.components import sleepiq
from homeassistant.components.binary_sensor import BinarySensorDevice
-DEPENDENCIES = ['sleepiq']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the SleepIQ sensors."""
diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json
new file mode 100644
index 00000000000..339685d32e1
--- /dev/null
+++ b/homeassistant/components/sleepiq/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sleepiq",
+ "name": "Sleepiq",
+ "documentation": "https://www.home-assistant.io/components/sleepiq",
+ "requirements": [
+ "sleepyq==0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py
index 2c97d7eb1e1..c92c463ea24 100644
--- a/homeassistant/components/sleepiq/sensor.py
+++ b/homeassistant/components/sleepiq/sensor.py
@@ -1,12 +1,6 @@
-"""
-Support for SleepIQ sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sleepiq/
-"""
+"""Support for SleepIQ sensors."""
from homeassistant.components import sleepiq
-DEPENDENCIES = ['sleepiq']
ICON = 'mdi:hotel'
diff --git a/homeassistant/components/sma/manifest.json b/homeassistant/components/sma/manifest.json
new file mode 100644
index 00000000000..e5e7a5bf446
--- /dev/null
+++ b/homeassistant/components/sma/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "sma",
+ "name": "Sma",
+ "documentation": "https://www.home-assistant.io/components/sma",
+ "requirements": [
+ "pysma==0.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@kellerza"
+ ]
+}
diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py
index 61009a472fb..9b6406484df 100644
--- a/homeassistant/components/sma/sensor.py
+++ b/homeassistant/components/sma/sensor.py
@@ -1,9 +1,4 @@
-"""
-SMA Solar Webconnect interface.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.sma/
-"""
+"""SMA Solar Webconnect interface."""
import asyncio
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval
-REQUIREMENTS = ['pysma==0.3.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_CUSTOM = 'custom'
diff --git a/homeassistant/components/smappee/__init__.py b/homeassistant/components/smappee/__init__.py
index 7a495d7b89a..c3f739b7b72 100644
--- a/homeassistant/components/smappee/__init__.py
+++ b/homeassistant/components/smappee/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.util import Throttle
from homeassistant.helpers.discovery import load_platform
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['smappy==0.2.16']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Smappee'
diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json
new file mode 100644
index 00000000000..361802f312e
--- /dev/null
+++ b/homeassistant/components/smappee/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "smappee",
+ "name": "Smappee",
+ "documentation": "https://www.home-assistant.io/components/smappee",
+ "requirements": [
+ "smappy==0.2.16"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py
index 98527c769d9..e34ede56e13 100644
--- a/homeassistant/components/smappee/sensor.py
+++ b/homeassistant/components/smappee/sensor.py
@@ -7,8 +7,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_SMAPPEE
-DEPENDENCIES = ['smappee']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_PREFIX = 'Smappee'
diff --git a/homeassistant/components/smappee/switch.py b/homeassistant/components/smappee/switch.py
index 963caf457fe..25ae9a612ce 100644
--- a/homeassistant/components/smappee/switch.py
+++ b/homeassistant/components/smappee/switch.py
@@ -5,8 +5,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DATA_SMAPPEE
-DEPENDENCIES = ['smappee']
-
_LOGGER = logging.getLogger(__name__)
ICON = 'mdi:power-plug'
diff --git a/homeassistant/components/smartthings/.translations/es.json b/homeassistant/components/smartthings/.translations/es.json
index 4edeb153921..5534b4e3bb3 100644
--- a/homeassistant/components/smartthings/.translations/es.json
+++ b/homeassistant/components/smartthings/.translations/es.json
@@ -3,18 +3,23 @@
"error": {
"app_not_installed": "Por favor aseg\u00farese de haber instalado y autorizado Home Assistant SmartApp y vuelva a intentarlo.",
"app_setup_error": "No se pudo configurar el SmartApp. Por favor, int\u00e9ntelo de nuevo.",
+ "base_url_not_https": "La 'base_url' del componente 'http' debe empezar por 'https://'.",
"token_already_setup": "El token ya ha sido configurado.",
+ "token_forbidden": "El token no tiene los alcances necesarios de OAuth.",
"token_invalid_format": "El token debe estar en formato UID/GUID",
- "token_unauthorized": "El token no es v\u00e1lido o ya no est\u00e1 autorizado."
+ "token_unauthorized": "El token no es v\u00e1lido o ya no est\u00e1 autorizado.",
+ "webhook_error": "SmartThings no ha podido validar el endpoint configurado en 'base_url'. Por favor, revisa los requisitos del componente."
},
"step": {
"user": {
"data": {
"access_token": "Token de acceso"
},
- "title": "Ingresar token de acceso personal"
+ "description": "Por favor, introduce el [token de acceso personal]({token_url}) de SmartThings que se haya creado seg\u00fan las [instrucciones]({component_url}).",
+ "title": "Introduce el token de acceso personal"
},
"wait_install": {
+ "description": "Por favor, instala Home Assistant SmartApp en al menos una ubicaci\u00f3n y pulsa en enviar.",
"title": "Instalar SmartApp"
}
},
diff --git a/homeassistant/components/smartthings/.translations/ru.json b/homeassistant/components/smartthings/.translations/ru.json
index 6e34cf8a49a..575c593d5a4 100644
--- a/homeassistant/components/smartthings/.translations/ru.json
+++ b/homeassistant/components/smartthings/.translations/ru.json
@@ -16,7 +16,7 @@
"access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430"
},
"description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 [\u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430]({token_url}) SmartThings, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({component_url}).",
- "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430"
+ "title": "SmartThings"
},
"wait_install": {
"description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 SmartApp 'Home Assistant' \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**.",
diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py
index e5226076f46..f2f1021ff66 100644
--- a/homeassistant/components/smartthings/__init__.py
+++ b/homeassistant/components/smartthings/__init__.py
@@ -28,9 +28,6 @@ from .smartapp import (
unload_smartapp_endpoint, validate_installed_app,
validate_webhook_requirements)
-REQUIREMENTS = ['pysmartapp==0.3.2', 'pysmartthings==0.6.7']
-DEPENDENCIES = ['webhook']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py
index 45101601d5f..39ff2999e3a 100644
--- a/homeassistant/components/smartthings/binary_sensor.py
+++ b/homeassistant/components/smartthings/binary_sensor.py
@@ -6,8 +6,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
CAPABILITY_TO_ATTRIB = {
'accelerationSensor': 'acceleration',
'contactSensor': 'contact',
diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py
index bcf2dc02cb0..f45ea10ce47 100644
--- a/homeassistant/components/smartthings/climate.py
+++ b/homeassistant/components/smartthings/climate.py
@@ -17,8 +17,6 @@ from homeassistant.const import (
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
ATTR_OPERATION_STATE = 'operation_state'
MODE_TO_STATE = {
'auto': STATE_AUTO,
diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py
index 53602c3643c..47116ad3dd6 100644
--- a/homeassistant/components/smartthings/cover.py
+++ b/homeassistant/components/smartthings/cover.py
@@ -11,8 +11,6 @@ from homeassistant.const import ATTR_BATTERY_LEVEL
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
VALUE_TO_STATE = {
'closed': STATE_CLOSED,
'closing': STATE_CLOSING,
diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py
index e722cd21d65..befcb3fcb78 100644
--- a/homeassistant/components/smartthings/fan.py
+++ b/homeassistant/components/smartthings/fan.py
@@ -8,8 +8,6 @@ from homeassistant.components.fan import (
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
VALUE_TO_SPEED = {
0: SPEED_OFF,
1: SPEED_LOW,
diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py
index 79a5eabc20a..6e609b4b53c 100644
--- a/homeassistant/components/smartthings/light.py
+++ b/homeassistant/components/smartthings/light.py
@@ -11,8 +11,6 @@ import homeassistant.util.color as color_util
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py
index c7ab091454c..ca2e45114d9 100644
--- a/homeassistant/components/smartthings/lock.py
+++ b/homeassistant/components/smartthings/lock.py
@@ -6,8 +6,6 @@ from homeassistant.components.lock import LockDevice
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
ST_STATE_LOCKED = 'locked'
ST_LOCK_ATTR_MAP = {
'codeId': 'code_id',
diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json
new file mode 100644
index 00000000000..d4baf69b108
--- /dev/null
+++ b/homeassistant/components/smartthings/manifest.json
@@ -0,0 +1,15 @@
+{
+ "domain": "smartthings",
+ "name": "Smartthings",
+ "documentation": "https://www.home-assistant.io/components/smartthings",
+ "requirements": [
+ "pysmartapp==0.3.2",
+ "pysmartthings==0.6.7"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": [
+ "@andrewsayre"
+ ]
+}
diff --git a/homeassistant/components/smartthings/scene.py b/homeassistant/components/smartthings/scene.py
index 9bf3211d8e3..17c7bd51b41 100644
--- a/homeassistant/components/smartthings/scene.py
+++ b/homeassistant/components/smartthings/scene.py
@@ -3,8 +3,6 @@ from homeassistant.components.scene import Scene
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py
index 4f7ad1a1398..4abb3e20c3e 100644
--- a/homeassistant/components/smartthings/sensor.py
+++ b/homeassistant/components/smartthings/sensor.py
@@ -10,8 +10,6 @@ from homeassistant.const import (
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
Map = namedtuple("map", "attribute name default_unit device_class")
CAPABILITY_TO_SENSORS = {
diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py
index 548a38711bd..9aa44d26f2d 100644
--- a/homeassistant/components/smartthings/smartapp.py
+++ b/homeassistant/components/smartthings/smartapp.py
@@ -1,10 +1,4 @@
-"""
-SmartApp functionality to receive cloud-push notifications.
-
-This module defines the functions to manage the SmartApp integration
-within the SmartThings ecosystem in order to receive real-time webhook-based
-callbacks when device states change.
-"""
+"""SmartApp functionality to receive cloud-push notifications."""
import asyncio
import functools
import logging
diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py
index d30aa3a2303..2149a87250e 100644
--- a/homeassistant/components/smartthings/switch.py
+++ b/homeassistant/components/smartthings/switch.py
@@ -6,8 +6,6 @@ from homeassistant.components.switch import SwitchDevice
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
-DEPENDENCIES = ['smartthings']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/smhi/__init__.py b/homeassistant/components/smhi/__init__.py
index 608ee9b6a6d..b62f110b619 100644
--- a/homeassistant/components/smhi/__init__.py
+++ b/homeassistant/components/smhi/__init__.py
@@ -6,8 +6,6 @@ from homeassistant.core import Config, HomeAssistant
from .config_flow import smhi_locations # noqa: F401
from .const import DOMAIN # noqa: F401
-REQUIREMENTS = ['smhi-pkg==1.0.10']
-
DEFAULT_NAME = 'smhi'
diff --git a/homeassistant/components/smhi/manifest.json b/homeassistant/components/smhi/manifest.json
new file mode 100644
index 00000000000..e4ad478e033
--- /dev/null
+++ b/homeassistant/components/smhi/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "smhi",
+ "name": "Smhi",
+ "documentation": "https://www.home-assistant.io/components/smhi",
+ "requirements": [
+ "smhi-pkg==1.0.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py
index fc3399f755c..ab5d08e770b 100644
--- a/homeassistant/components/smhi/weather.py
+++ b/homeassistant/components/smhi/weather.py
@@ -19,8 +19,6 @@ from homeassistant.util import Throttle, slugify
from .const import ATTR_SMHI_CLOUDINESS, ENTITY_ID_SENSOR_FORMAT
-DEPENDENCIES = ['smhi']
-
_LOGGER = logging.getLogger(__name__)
# Used to map condition from API results
diff --git a/homeassistant/components/smtp/manifest.json b/homeassistant/components/smtp/manifest.json
new file mode 100644
index 00000000000..2e1a8d826ce
--- /dev/null
+++ b/homeassistant/components/smtp/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "smtp",
+ "name": "Smtp",
+ "documentation": "https://www.home-assistant.io/components/smtp",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py
index 4104013bcf7..1aaf3464e2b 100644
--- a/homeassistant/components/smtp/notify.py
+++ b/homeassistant/components/smtp/notify.py
@@ -1,9 +1,4 @@
-"""
-Mail (SMTP) notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.smtp/
-"""
+"""Mail (SMTP) notification service."""
from email.mime.application import MIMEApplication
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json
new file mode 100644
index 00000000000..70c9db7dada
--- /dev/null
+++ b/homeassistant/components/snapcast/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "snapcast",
+ "name": "Snapcast",
+ "documentation": "https://www.home-assistant.io/components/snapcast",
+ "requirements": [
+ "snapcast==2.0.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py
index 74b17ae5ff1..12ecabd68ea 100644
--- a/homeassistant/components/snapcast/media_player.py
+++ b/homeassistant/components/snapcast/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interacting with Snapcast clients.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.snapcast/
-"""
+"""Support for interacting with Snapcast clients."""
import logging
import socket
@@ -19,8 +14,6 @@ from homeassistant.const import (
STATE_PLAYING, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['snapcast==2.0.9']
-
_LOGGER = logging.getLogger(__name__)
DATA_KEY = 'snapcast'
diff --git a/homeassistant/components/snapcast/services.yaml b/homeassistant/components/snapcast/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/snips/__init__.py b/homeassistant/components/snips/__init__.py
index 0cc96d66b1a..71e37fa5137 100644
--- a/homeassistant/components/snips/__init__.py
+++ b/homeassistant/components/snips/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers import intent, config_validation as cv
from homeassistant.components import mqtt
DOMAIN = 'snips'
-DEPENDENCIES = ['mqtt']
-
CONF_INTENTS = 'intents'
CONF_ACTION = 'action'
CONF_FEEDBACK = 'feedback_sounds'
diff --git a/homeassistant/components/snips/manifest.json b/homeassistant/components/snips/manifest.json
new file mode 100644
index 00000000000..58fddb7a3f4
--- /dev/null
+++ b/homeassistant/components/snips/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "snips",
+ "name": "Snips",
+ "documentation": "https://www.home-assistant.io/components/snips",
+ "requirements": [],
+ "dependencies": [
+ "mqtt"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/snmp/device_tracker.py b/homeassistant/components/snmp/device_tracker.py
index 7c6efc82ef9..b36681161cb 100644
--- a/homeassistant/components/snmp/device_tracker.py
+++ b/homeassistant/components/snmp/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for fetching WiFi associations through SNMP.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.snmp/
-"""
+"""Support for fetching WiFi associations through SNMP."""
import binascii
import logging
@@ -14,8 +9,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
-REQUIREMENTS = ['pysnmp==4.4.8']
-
_LOGGER = logging.getLogger(__name__)
CONF_AUTHKEY = 'authkey'
diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json
new file mode 100644
index 00000000000..aeaa3451683
--- /dev/null
+++ b/homeassistant/components/snmp/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "snmp",
+ "name": "Snmp",
+ "documentation": "https://www.home-assistant.io/components/snmp",
+ "requirements": [
+ "pysnmp==4.4.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/snmp/sensor.py b/homeassistant/components/snmp/sensor.py
index 3964e44e376..df132140c38 100644
--- a/homeassistant/components/snmp/sensor.py
+++ b/homeassistant/components/snmp/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for displaying collected data over SNMP.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.snmp/
-"""
+"""Support for displaying collected data over SNMP."""
import logging
from datetime import timedelta
@@ -16,8 +11,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN,
CONF_USERNAME, CONF_VALUE_TEMPLATE)
-REQUIREMENTS = ['pysnmp==4.4.8']
-
_LOGGER = logging.getLogger(__name__)
CONF_BASEOID = 'baseoid'
diff --git a/homeassistant/components/snmp/switch.py b/homeassistant/components/snmp/switch.py
index 0baa129657d..5555f511272 100644
--- a/homeassistant/components/snmp/switch.py
+++ b/homeassistant/components/snmp/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for SNMP enabled switch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.snmp/
-"""
+"""Support for SNMP enabled switch."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.const import (
CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pysnmp==4.4.8']
-
_LOGGER = logging.getLogger(__name__)
CONF_BASEOID = 'baseoid'
diff --git a/homeassistant/components/sochain/manifest.json b/homeassistant/components/sochain/manifest.json
new file mode 100644
index 00000000000..23fad3683cb
--- /dev/null
+++ b/homeassistant/components/sochain/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sochain",
+ "name": "Sochain",
+ "documentation": "https://www.home-assistant.io/components/sochain",
+ "requirements": [
+ "python-sochain-api==0.0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sochain/sensor.py b/homeassistant/components/sochain/sensor.py
index ef6a53b7091..3f74e43d140 100644
--- a/homeassistant/components/sochain/sensor.py
+++ b/homeassistant/components/sochain/sensor.py
@@ -10,8 +10,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-sochain-api==0.0.2']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by chain.so"
diff --git a/homeassistant/components/socialblade/manifest.json b/homeassistant/components/socialblade/manifest.json
new file mode 100644
index 00000000000..e800bd7266a
--- /dev/null
+++ b/homeassistant/components/socialblade/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "socialblade",
+ "name": "Socialblade",
+ "documentation": "https://www.home-assistant.io/components/socialblade",
+ "requirements": [
+ "socialbladeclient==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/socialblade/sensor.py b/homeassistant/components/socialblade/sensor.py
index 9a73e9cdd68..a563de83f2d 100644
--- a/homeassistant/components/socialblade/sensor.py
+++ b/homeassistant/components/socialblade/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Social Blade.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.socialblade/
-"""
+"""Support for Social Blade."""
from datetime import timedelta
import logging
@@ -17,8 +12,6 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['socialbladeclient==0.2']
-
CHANNEL_ID = 'channel_id'
DEFAULT_NAME = "Social Blade"
diff --git a/homeassistant/components/solaredge/manifest.json b/homeassistant/components/solaredge/manifest.json
new file mode 100644
index 00000000000..b2707a0a937
--- /dev/null
+++ b/homeassistant/components/solaredge/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "solaredge",
+ "name": "Solaredge",
+ "documentation": "https://www.home-assistant.io/components/solaredge",
+ "requirements": [
+ "solaredge==0.0.2",
+ "stringcase==1.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py
index d56ccc53b68..da0eba1320f 100644
--- a/homeassistant/components/solaredge/sensor.py
+++ b/homeassistant/components/solaredge/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for SolarEdge Monitoring API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.solaredge/
-"""
+"""Support for SolarEdge Monitoring API."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['solaredge==0.0.2', 'stringcase==1.2.0']
-
# Config for solaredge monitoring api requests.
CONF_SITE_ID = "site_id"
diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py
new file mode 100755
index 00000000000..19d41833510
--- /dev/null
+++ b/homeassistant/components/somfy_mylink/__init__.py
@@ -0,0 +1,62 @@
+"""Component for the Somfy MyLink device supporting the Synergy API."""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import CONF_HOST, CONF_PORT
+from homeassistant.helpers import config_validation as cv
+from homeassistant.helpers.discovery import async_load_platform
+
+_LOGGER = logging.getLogger(__name__)
+CONF_ENTITY_CONFIG = 'entity_config'
+CONF_SYSTEM_ID = 'system_id'
+CONF_REVERSE = 'reverse'
+CONF_DEFAULT_REVERSE = 'default_reverse'
+DATA_SOMFY_MYLINK = 'somfy_mylink_data'
+DOMAIN = 'somfy_mylink'
+SOMFY_MYLINK_COMPONENTS = [
+ 'cover'
+]
+
+
+def validate_entity_config(values):
+ """Validate config entry for CONF_ENTITY."""
+ entity_config_schema = vol.Schema({
+ vol.Optional(CONF_REVERSE): cv.boolean
+ })
+ if not isinstance(values, dict):
+ raise vol.Invalid('expected a dictionary')
+ entities = {}
+ for entity_id, config in values.items():
+ entity = cv.entity_id(entity_id)
+ config = entity_config_schema(config)
+ entities[entity] = config
+ return entities
+
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_SYSTEM_ID): cv.string,
+ vol.Required(CONF_HOST): cv.string,
+ vol.Optional(CONF_PORT, default=44100): cv.port,
+ vol.Optional(CONF_DEFAULT_REVERSE, default=False): cv.boolean,
+ vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+
+async def async_setup(hass, config):
+ """Set up the MyLink platform."""
+ from somfy_mylink_synergy import SomfyMyLinkSynergy
+ host = config[DOMAIN][CONF_HOST]
+ port = config[DOMAIN][CONF_PORT]
+ system_id = config[DOMAIN][CONF_SYSTEM_ID]
+ entity_config = config[DOMAIN][CONF_ENTITY_CONFIG]
+ entity_config[CONF_DEFAULT_REVERSE] = config[DOMAIN][CONF_DEFAULT_REVERSE]
+ somfy_mylink = SomfyMyLinkSynergy(system_id, host, port)
+ hass.data[DATA_SOMFY_MYLINK] = somfy_mylink
+ for component in SOMFY_MYLINK_COMPONENTS:
+ hass.async_create_task(async_load_platform(
+ hass, component, DOMAIN, entity_config,
+ config))
+ return True
diff --git a/homeassistant/components/somfy_mylink/cover.py b/homeassistant/components/somfy_mylink/cover.py
new file mode 100755
index 00000000000..16046d8b411
--- /dev/null
+++ b/homeassistant/components/somfy_mylink/cover.py
@@ -0,0 +1,89 @@
+"""Cover Platform for the Somfy MyLink component."""
+import logging
+
+from homeassistant.components.cover import ENTITY_ID_FORMAT, CoverDevice
+from homeassistant.util import slugify
+
+from . import CONF_DEFAULT_REVERSE, DATA_SOMFY_MYLINK
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_platform(hass,
+ config,
+ async_add_entities,
+ discovery_info=None):
+ """Discover and configure Somfy covers."""
+ if discovery_info is None:
+ return
+ somfy_mylink = hass.data[DATA_SOMFY_MYLINK]
+ cover_list = []
+ try:
+ mylink_status = await somfy_mylink.status_info()
+ except TimeoutError:
+ _LOGGER.error("Unable to connect to the Somfy MyLink device, "
+ "please check your settings")
+ return
+ for cover in mylink_status['result']:
+ entity_id = ENTITY_ID_FORMAT.format(slugify(cover['name']))
+ entity_config = discovery_info.get(entity_id, {})
+ default_reverse = discovery_info[CONF_DEFAULT_REVERSE]
+ cover_config = {}
+ cover_config['target_id'] = cover['targetID']
+ cover_config['name'] = cover['name']
+ cover_config['reverse'] = entity_config.get('reverse', default_reverse)
+ cover_list.append(SomfyShade(somfy_mylink, **cover_config))
+ _LOGGER.info('Adding Somfy Cover: %s with targetID %s',
+ cover_config['name'], cover_config['target_id'])
+ async_add_entities(cover_list)
+
+
+class SomfyShade(CoverDevice):
+ """Object for controlling a Somfy cover."""
+
+ def __init__(self, somfy_mylink, target_id='AABBCC', name='SomfyShade',
+ reverse=False, device_class='window'):
+ """Initialize the cover."""
+ self.somfy_mylink = somfy_mylink
+ self._target_id = target_id
+ self._name = name
+ self._reverse = reverse
+ self._device_class = device_class
+
+ @property
+ def name(self):
+ """Return the name of the cover."""
+ return self._name
+
+ @property
+ def is_closed(self):
+ """Return if the cover is closed."""
+ return None
+
+ @property
+ def assumed_state(self):
+ """Let HA know the integration is assumed state."""
+ return True
+
+ @property
+ def device_class(self):
+ """Return the class of this device, from component DEVICE_CLASSES."""
+ return self._device_class
+
+ async def async_open_cover(self, **kwargs):
+ """Wrap Homeassistant calls to open the cover."""
+ if not self._reverse:
+ await self.somfy_mylink.move_up(self._target_id)
+ else:
+ await self.somfy_mylink.move_down(self._target_id)
+
+ async def async_close_cover(self, **kwargs):
+ """Wrap Homeassistant calls to close the cover."""
+ if not self._reverse:
+ await self.somfy_mylink.move_down(self._target_id)
+ else:
+ await self.somfy_mylink.move_up(self._target_id)
+
+ async def async_stop_cover(self, **kwargs):
+ """Stop the cover."""
+ await self.somfy_mylink.move_stop(self._target_id)
diff --git a/homeassistant/components/somfy_mylink/manifest.json b/homeassistant/components/somfy_mylink/manifest.json
new file mode 100644
index 00000000000..5a3cec0def8
--- /dev/null
+++ b/homeassistant/components/somfy_mylink/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "somfy_mylink",
+ "name": "Somfy MyLink",
+ "documentation": "https://www.home-assistant.io/components/somfy_mylink",
+ "requirements": [
+ "somfy-mylink-synergy==1.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+ }
\ No newline at end of file
diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json
new file mode 100644
index 00000000000..bc0235ec5b3
--- /dev/null
+++ b/homeassistant/components/sonarr/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "sonarr",
+ "name": "Sonarr",
+ "documentation": "https://www.home-assistant.io/components/sonarr",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py
index b0e87992e39..b593f6d3182 100644
--- a/homeassistant/components/sonarr/sensor.py
+++ b/homeassistant/components/sonarr/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Sonarr.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.sonarr/
-"""
+"""Support for Sonarr."""
import logging
import time
from datetime import datetime
diff --git a/homeassistant/components/songpal/manifest.json b/homeassistant/components/songpal/manifest.json
new file mode 100644
index 00000000000..0d1af7053b2
--- /dev/null
+++ b/homeassistant/components/songpal/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "songpal",
+ "name": "Songpal",
+ "documentation": "https://www.home-assistant.io/components/songpal",
+ "requirements": [
+ "python-songpal==0.0.9.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py
index 7665b409d1d..077975b26e2 100644
--- a/homeassistant/components/songpal/media_player.py
+++ b/homeassistant/components/songpal/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Songpal-enabled (Sony) media devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.songpal/
-"""
+"""Support for Songpal-enabled (Sony) media devices."""
import asyncio
import logging
from collections import OrderedDict
@@ -21,8 +16,6 @@ from homeassistant.const import (
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-songpal==0.0.9.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ENDPOINT = 'endpoint'
diff --git a/homeassistant/components/songpal/services.yaml b/homeassistant/components/songpal/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/sonos/.translations/ko.json b/homeassistant/components/sonos/.translations/ko.json
index 0b2e2a1875c..4ca3d621599 100644
--- a/homeassistant/components/sonos/.translations/ko.json
+++ b/homeassistant/components/sonos/.translations/ko.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "no_devices_found": "Sonos \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
+ "no_devices_found": "Sonos \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "\ud558\ub098\uc758 Sonos \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"step": {
diff --git a/homeassistant/components/sonos/.translations/nn.json b/homeassistant/components/sonos/.translations/nn.json
index f2451efaff4..e7df1f23f20 100644
--- a/homeassistant/components/sonos/.translations/nn.json
+++ b/homeassistant/components/sonos/.translations/nn.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "no_devices_found": "Det vart ikkje funne noko Sonoseiningar p\u00e5 nettverket.",
+ "no_devices_found": "Det vart ikkje funne noko Sonos-einingar p\u00e5 nettverket.",
"single_instance_allowed": "Du treng berre \u00e5 sette opp \u00e9in Sonos-konfigurasjon."
},
"step": {
diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py
index e9f297e4f07..b661fa26fe7 100644
--- a/homeassistant/components/sonos/__init__.py
+++ b/homeassistant/components/sonos/__init__.py
@@ -4,7 +4,6 @@ from homeassistant.helpers import config_entry_flow
DOMAIN = 'sonos'
-REQUIREMENTS = ['pysonos==0.0.8']
async def async_setup(hass, config):
diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json
new file mode 100644
index 00000000000..b2598bc5be9
--- /dev/null
+++ b/homeassistant/components/sonos/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "sonos",
+ "name": "Sonos",
+ "documentation": "https://www.home-assistant.io/components/sonos",
+ "requirements": [
+ "pysonos==0.0.10"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@amelchio"
+ ]
+}
diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py
index ba7854e4f0d..7c2e5fec843 100644
--- a/homeassistant/components/sonos/media_player.py
+++ b/homeassistant/components/sonos/media_player.py
@@ -454,18 +454,7 @@ class SonosEntity(MediaPlayerDevice):
def _set_favorites(self):
"""Set available favorites."""
- # SoCo 0.16 raises a generic Exception on invalid xml in favorites.
- # Filter those out now so our list is safe to use.
- try:
- self._favorites = []
- for fav in self.soco.music_library.get_sonos_favorites():
- try:
- if fav.reference.get_uri():
- self._favorites.append(fav)
- except Exception: # pylint: disable=broad-except
- _LOGGER.debug("Ignoring invalid favorite '%s'", fav.title)
- except Exception: # pylint: disable=broad-except
- _LOGGER.debug("Ignoring invalid favorite list")
+ self._favorites = self.soco.music_library.get_sonos_favorites()
def _radio_artwork(self, url):
"""Return the private URL with artwork for a radio stream."""
diff --git a/homeassistant/components/sonos/services.yaml b/homeassistant/components/sonos/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/sony_projector/manifest.json b/homeassistant/components/sony_projector/manifest.json
new file mode 100644
index 00000000000..1cc25d93f59
--- /dev/null
+++ b/homeassistant/components/sony_projector/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sony_projector",
+ "name": "Sony projector",
+ "documentation": "https://www.home-assistant.io/components/sony_projector",
+ "requirements": [
+ "pysdcp==1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/sony_projector/switch.py b/homeassistant/components/sony_projector/switch.py
index 5b3ffeed75f..9ffd8ffb8bf 100644
--- a/homeassistant/components/sony_projector/switch.py
+++ b/homeassistant/components/sony_projector/switch.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
STATE_ON, STATE_OFF, CONF_NAME, CONF_HOST)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pysdcp==1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Sony Projector'
diff --git a/homeassistant/components/soundtouch/manifest.json b/homeassistant/components/soundtouch/manifest.json
new file mode 100644
index 00000000000..eba60bc6e34
--- /dev/null
+++ b/homeassistant/components/soundtouch/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "soundtouch",
+ "name": "Soundtouch",
+ "documentation": "https://www.home-assistant.io/components/soundtouch",
+ "requirements": [
+ "libsoundtouch==0.7.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py
index b2045b9b65e..a2a6c315eda 100644
--- a/homeassistant/components/soundtouch/media_player.py
+++ b/homeassistant/components/soundtouch/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with a Bose Soundtouch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.soundtouch/
-"""
+"""Support for interface with a Bose Soundtouch."""
import logging
import re
@@ -20,8 +15,6 @@ from homeassistant.const import (
STATE_UNAVAILABLE)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['libsoundtouch==0.7.2']
-
_LOGGER = logging.getLogger(__name__)
SERVICE_PLAY_EVERYWHERE = 'soundtouch_play_everywhere'
diff --git a/homeassistant/components/soundtouch/services.yaml b/homeassistant/components/soundtouch/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py
index fb76718f2d5..5431cd6260c 100644
--- a/homeassistant/components/spaceapi/__init__.py
+++ b/homeassistant/components/spaceapi/__init__.py
@@ -45,7 +45,6 @@ CONF_TEMPERATURE = 'temperature'
CONF_TWITTER = 'twitter'
DATA_SPACEAPI = 'data_spaceapi'
-DEPENDENCIES = ['http']
DOMAIN = 'spaceapi'
ISSUE_REPORT_CHANNELS = [CONF_EMAIL, CONF_IRC, CONF_MAILING_LIST, CONF_TWITTER]
diff --git a/homeassistant/components/spaceapi/manifest.json b/homeassistant/components/spaceapi/manifest.json
new file mode 100644
index 00000000000..03aa5c0a1f7
--- /dev/null
+++ b/homeassistant/components/spaceapi/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "spaceapi",
+ "name": "Spaceapi",
+ "documentation": "https://www.home-assistant.io/components/spaceapi",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/spc/__init__.py b/homeassistant/components/spc/__init__.py
index 8aafb6f1210..8e06e254661 100644
--- a/homeassistant/components/spc/__init__.py
+++ b/homeassistant/components/spc/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.helpers import discovery, aiohttp_client
from homeassistant.helpers.dispatcher import async_dispatcher_send
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyspcwebgw==0.4.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_WS_URL = 'ws_url'
diff --git a/homeassistant/components/spc/alarm_control_panel.py b/homeassistant/components/spc/alarm_control_panel.py
index 623a4b0dbd1..77b412021aa 100644
--- a/homeassistant/components/spc/alarm_control_panel.py
+++ b/homeassistant/components/spc/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Support for Vanderbilt (formerly Siemens) SPC alarm systems.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.spc/
-"""
+"""Support for Vanderbilt (formerly Siemens) SPC alarm systems."""
import logging
import homeassistant.components.alarm_control_panel as alarm
diff --git a/homeassistant/components/spc/binary_sensor.py b/homeassistant/components/spc/binary_sensor.py
index 6a0712d62bb..78ec2a11a97 100644
--- a/homeassistant/components/spc/binary_sensor.py
+++ b/homeassistant/components/spc/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Vanderbilt (formerly Siemens) SPC alarm systems.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.spc/
-"""
+"""Support for Vanderbilt (formerly Siemens) SPC alarm systems."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
diff --git a/homeassistant/components/spc/manifest.json b/homeassistant/components/spc/manifest.json
new file mode 100644
index 00000000000..572d4b04b87
--- /dev/null
+++ b/homeassistant/components/spc/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "spc",
+ "name": "Spc",
+ "documentation": "https://www.home-assistant.io/components/spc",
+ "requirements": [
+ "pyspcwebgw==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py
index f140f881ef4..83890295018 100644
--- a/homeassistant/components/speedtestdotnet/__init__.py
+++ b/homeassistant/components/speedtestdotnet/__init__.py
@@ -1,22 +1,19 @@
"""Support for testing internet speed via Speedtest.net."""
-from datetime import timedelta
import logging
+from datetime import timedelta
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import (
- CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL, CONF_UPDATE_INTERVAL,
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
-import homeassistant.helpers.config_validation as cv
+ CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL
+)
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
-
from .const import DATA_UPDATED, DOMAIN, SENSOR_TYPES
-REQUIREMENTS = ['speedtest-cli==2.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_SERVER_ID = 'server_id'
@@ -25,25 +22,15 @@ CONF_MANUAL = 'manual'
DEFAULT_INTERVAL = timedelta(hours=1)
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(
- vol.Schema({
- vol.Optional(CONF_SERVER_ID): cv.positive_int,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
- vol.All(cv.time_period, cv.positive_timedelta),
- vol.Optional(CONF_MANUAL, default=False): cv.boolean,
- vol.Optional(
- CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)
- ): vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))])
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=DEFAULT_INTERVAL
- )
- )
+ DOMAIN: vol.Schema({
+ vol.Optional(CONF_SERVER_ID): cv.positive_int,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
+ vol.All(cv.time_period, cv.positive_timedelta),
+ vol.Optional(CONF_MANUAL, default=False): cv.boolean,
+ vol.Optional(
+ CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)
+ ): vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))])
+ })
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/speedtestdotnet/manifest.json b/homeassistant/components/speedtestdotnet/manifest.json
new file mode 100644
index 00000000000..91b7e7c5c0f
--- /dev/null
+++ b/homeassistant/components/speedtestdotnet/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "speedtestdotnet",
+ "name": "Speedtestdotnet",
+ "documentation": "https://www.home-assistant.io/components/speedtestdotnet",
+ "requirements": [
+ "speedtest-cli==2.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py
index fb92bb76ac8..785b981f1ac 100644
--- a/homeassistant/components/speedtestdotnet/sensor.py
+++ b/homeassistant/components/speedtestdotnet/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.helpers.restore_state import RestoreEntity
from .const import (
DATA_UPDATED, DOMAIN as SPEEDTESTDOTNET_DOMAIN, SENSOR_TYPES)
-DEPENDENCIES = ['speedtestdotnet']
-
_LOGGER = logging.getLogger(__name__)
ATTR_BYTES_RECEIVED = 'bytes_received'
diff --git a/homeassistant/components/spider/__init__.py b/homeassistant/components/spider/__init__.py
index b565f183457..aadbfc8eb9b 100644
--- a/homeassistant/components/spider/__init__.py
+++ b/homeassistant/components/spider/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
-REQUIREMENTS = ['spiderpy==1.3.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'spider'
diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py
index 3b612441a88..069f34da3f7 100644
--- a/homeassistant/components/spider/climate.py
+++ b/homeassistant/components/spider/climate.py
@@ -10,8 +10,6 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from . import DOMAIN as SPIDER_DOMAIN
-DEPENDENCIES = ['spider']
-
FAN_LIST = [
'Auto',
'Low',
diff --git a/homeassistant/components/spider/manifest.json b/homeassistant/components/spider/manifest.json
new file mode 100644
index 00000000000..4cd7a467737
--- /dev/null
+++ b/homeassistant/components/spider/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "spider",
+ "name": "Spider",
+ "documentation": "https://www.home-assistant.io/components/spider",
+ "requirements": [
+ "spiderpy==1.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@peternijssen"
+ ]
+}
diff --git a/homeassistant/components/spider/switch.py b/homeassistant/components/spider/switch.py
index e43762be460..286ea3e7ddf 100644
--- a/homeassistant/components/spider/switch.py
+++ b/homeassistant/components/spider/switch.py
@@ -5,8 +5,6 @@ from homeassistant.components.switch import SwitchDevice
from . import DOMAIN as SPIDER_DOMAIN
-DEPENDENCIES = ['spider']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/splunk/manifest.json b/homeassistant/components/splunk/manifest.json
new file mode 100644
index 00000000000..2e81da3409a
--- /dev/null
+++ b/homeassistant/components/splunk/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "splunk",
+ "name": "Splunk",
+ "documentation": "https://www.home-assistant.io/components/splunk",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/spotcrime/manifest.json b/homeassistant/components/spotcrime/manifest.json
new file mode 100644
index 00000000000..49b8742c53e
--- /dev/null
+++ b/homeassistant/components/spotcrime/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "spotcrime",
+ "name": "Spotcrime",
+ "documentation": "https://www.home-assistant.io/components/spotcrime",
+ "requirements": [
+ "spotcrime==1.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/spotcrime/sensor.py b/homeassistant/components/spotcrime/sensor.py
index 46f5fdc1c85..a5636f543a3 100644
--- a/homeassistant/components/spotcrime/sensor.py
+++ b/homeassistant/components/spotcrime/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for Spot Crime.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.spotcrime/
-"""
+"""Sensor for Spot Crime."""
from datetime import timedelta
from collections import defaultdict
@@ -20,8 +15,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['spotcrime==1.0.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_DAYS = 'days'
diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json
new file mode 100644
index 00000000000..366a5eef0ad
--- /dev/null
+++ b/homeassistant/components/spotify/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "spotify",
+ "name": "Spotify",
+ "documentation": "https://www.home-assistant.io/components/spotify",
+ "requirements": [
+ "spotipy-homeassistant==2.4.4.dev1"
+ ],
+ "dependencies": [
+ "configurator",
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py
index 9965487ded9..d6014008c76 100644
--- a/homeassistant/components/spotify/media_player.py
+++ b/homeassistant/components/spotify/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interacting with Spotify Connect.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.spotify/
-"""
+"""Support for interacting with Spotify Connect."""
from datetime import timedelta
import logging
@@ -21,8 +16,6 @@ from homeassistant.const import (
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['spotipy-homeassistant==2.4.4.dev1']
-
_LOGGER = logging.getLogger(__name__)
AUTH_CALLBACK_NAME = 'api:spotify'
@@ -40,7 +33,6 @@ CONFIGURATOR_SUBMIT_CAPTION = 'I authorized successfully'
DEFAULT_CACHE_PATH = '.spotify-token-cache'
DEFAULT_NAME = 'Spotify'
-DEPENDENCIES = ['http']
DOMAIN = 'spotify'
ICON = 'mdi:spotify'
diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json
new file mode 100644
index 00000000000..9a26e676018
--- /dev/null
+++ b/homeassistant/components/sql/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "sql",
+ "name": "Sql",
+ "documentation": "https://www.home-assistant.io/components/sql",
+ "requirements": [
+ "sqlalchemy==1.3.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py
index bc40d5efb42..475bde97de4 100644
--- a/homeassistant/components/sql/sensor.py
+++ b/homeassistant/components/sql/sensor.py
@@ -15,8 +15,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['sqlalchemy==1.3.0']
-
CONF_COLUMN_NAME = 'column'
CONF_QUERIES = 'queries'
CONF_QUERY = 'query'
diff --git a/homeassistant/components/squeezebox/manifest.json b/homeassistant/components/squeezebox/manifest.json
new file mode 100644
index 00000000000..ae124d6c03d
--- /dev/null
+++ b/homeassistant/components/squeezebox/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "squeezebox",
+ "name": "Squeezebox",
+ "documentation": "https://www.home-assistant.io/components/squeezebox",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py
index 5f6fd525a11..d25d2f03fce 100644
--- a/homeassistant/components/squeezebox/media_player.py
+++ b/homeassistant/components/squeezebox/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interfacing to the Logitech SqueezeBox API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.squeezebox/
-"""
+"""Support for interfacing to the Logitech SqueezeBox API."""
import asyncio
import json
import logging
diff --git a/homeassistant/components/squeezebox/services.yaml b/homeassistant/components/squeezebox/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/srp_energy/manifest.json b/homeassistant/components/srp_energy/manifest.json
new file mode 100644
index 00000000000..050a78223c1
--- /dev/null
+++ b/homeassistant/components/srp_energy/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "srp_energy",
+ "name": "Srp energy",
+ "documentation": "https://www.home-assistant.io/components/srp_energy",
+ "requirements": [
+ "srpenergy==1.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/srp_energy/sensor.py b/homeassistant/components/srp_energy/sensor.py
index 4d2cd863b12..a84295fdef9 100644
--- a/homeassistant/components/srp_energy/sensor.py
+++ b/homeassistant/components/srp_energy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Platform for retrieving energy data from SRP.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/sensor.srp_energy/
-"""
+"""Platform for retrieving energy data from SRP."""
from datetime import datetime, timedelta
import logging
@@ -19,8 +14,6 @@ from homeassistant.util import Throttle
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['srpenergy==1.0.6']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by SRP Energy"
diff --git a/homeassistant/components/starlingbank/manifest.json b/homeassistant/components/starlingbank/manifest.json
new file mode 100644
index 00000000000..1314fda5099
--- /dev/null
+++ b/homeassistant/components/starlingbank/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "starlingbank",
+ "name": "Starlingbank",
+ "documentation": "https://www.home-assistant.io/components/starlingbank",
+ "requirements": [
+ "starlingbank==3.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/starlingbank/sensor.py b/homeassistant/components/starlingbank/sensor.py
index e325e5e1a57..743bce5a736 100644
--- a/homeassistant/components/starlingbank/sensor.py
+++ b/homeassistant/components/starlingbank/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for balance data via the Starling Bank API.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/sensor.starlingbank/
-"""
+"""Support for balance data via the Starling Bank API."""
import logging
import requests
@@ -14,8 +9,6 @@ from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['starlingbank==3.1']
-
_LOGGER = logging.getLogger(__name__)
BALANCE_TYPES = ['cleared_balance', 'effective_balance']
diff --git a/homeassistant/components/startca/manifest.json b/homeassistant/components/startca/manifest.json
new file mode 100644
index 00000000000..1d13936f592
--- /dev/null
+++ b/homeassistant/components/startca/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "startca",
+ "name": "Startca",
+ "documentation": "https://www.home-assistant.io/components/startca",
+ "requirements": [
+ "xmltodict==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/startca/sensor.py b/homeassistant/components/startca/sensor.py
index 85939ea72ae..fe2c35c39b7 100644
--- a/homeassistant/components/startca/sensor.py
+++ b/homeassistant/components/startca/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Start.ca Bandwidth Monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.startca/
-"""
+"""Support for Start.ca Bandwidth Monitor."""
from datetime import timedelta
from xml.parsers.expat import ExpatError
import logging
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['xmltodict==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Start.ca'
diff --git a/homeassistant/components/statistics/manifest.json b/homeassistant/components/statistics/manifest.json
new file mode 100644
index 00000000000..49e476a6876
--- /dev/null
+++ b/homeassistant/components/statistics/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "statistics",
+ "name": "Statistics",
+ "documentation": "https://www.home-assistant.io/components/statistics",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py
index 01c783dc1db..a777a921f31 100644
--- a/homeassistant/components/statistics/sensor.py
+++ b/homeassistant/components/statistics/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for statistics for sensor values.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.statistics/
-"""
+"""Support for statistics for sensor values."""
import logging
import statistics
from collections import deque
diff --git a/homeassistant/components/statsd/__init__.py b/homeassistant/components/statsd/__init__.py
index a8c34d0a843..c1b7e8de68d 100644
--- a/homeassistant/components/statsd/__init__.py
+++ b/homeassistant/components/statsd/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import state as state_helper
-REQUIREMENTS = ['statsd==3.2.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ATTR = 'log_attributes'
diff --git a/homeassistant/components/statsd/manifest.json b/homeassistant/components/statsd/manifest.json
new file mode 100644
index 00000000000..20f4cc7f544
--- /dev/null
+++ b/homeassistant/components/statsd/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "statsd",
+ "name": "Statsd",
+ "documentation": "https://www.home-assistant.io/components/statsd",
+ "requirements": [
+ "statsd==3.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/steam_online/manifest.json b/homeassistant/components/steam_online/manifest.json
new file mode 100644
index 00000000000..735a1869c34
--- /dev/null
+++ b/homeassistant/components/steam_online/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "steam_online",
+ "name": "Steam online",
+ "documentation": "https://www.home-assistant.io/components/steam_online",
+ "requirements": [
+ "steamodd==4.21"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py
index 861a5958dd3..1afeb2be4df 100644
--- a/homeassistant/components/steam_online/sensor.py
+++ b/homeassistant/components/steam_online/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for Steam account status.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.steam_online/
-"""
+"""Sensor for Steam account status."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_API_KEY
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['steamodd==4.21']
-
_LOGGER = logging.getLogger(__name__)
CONF_ACCOUNTS = 'accounts'
diff --git a/homeassistant/components/stiebel_eltron/__init__.py b/homeassistant/components/stiebel_eltron/__init__.py
new file mode 100644
index 00000000000..52dc2d84891
--- /dev/null
+++ b/homeassistant/components/stiebel_eltron/__init__.py
@@ -0,0 +1,59 @@
+"""The component for STIEBEL ELTRON heat pumps with ISGWeb Modbus module."""
+from datetime import timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.modbus import (
+ CONF_HUB, DEFAULT_HUB, DOMAIN as MODBUS_DOMAIN)
+from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
+from homeassistant.helpers import discovery
+import homeassistant.helpers.config_validation as cv
+from homeassistant.util import Throttle
+
+DOMAIN = 'stiebel_eltron'
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_HUB, default=DEFAULT_HUB): cv.string,
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+_LOGGER = logging.getLogger(__name__)
+
+MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
+
+
+def setup(hass, config):
+ """Set up the STIEBEL ELTRON unit.
+
+ Will automatically load climate platform.
+ """
+ name = config[DOMAIN][CONF_NAME]
+ modbus_client = hass.data[MODBUS_DOMAIN][config[DOMAIN][CONF_HUB]]
+
+ hass.data[DOMAIN] = {
+ 'name': name,
+ 'ste_data': StiebelEltronData(name, modbus_client)
+ }
+
+ discovery.load_platform(hass, 'climate', DOMAIN, {}, config)
+ return True
+
+
+class StiebelEltronData:
+ """Get the latest data and update the states."""
+
+ def __init__(self, name, modbus_client):
+ """Init the STIEBEL ELTRON data object."""
+ from pystiebeleltron import pystiebeleltron
+ self.api = pystiebeleltron.StiebelEltronAPI(modbus_client, 1)
+
+ @Throttle(MIN_TIME_BETWEEN_UPDATES)
+ def update(self):
+ """Update unit data."""
+ if not self.api.update():
+ _LOGGER.warning("Modbus read failed")
+ else:
+ _LOGGER.debug("Data updated successfully")
diff --git a/homeassistant/components/stiebel_eltron/climate.py b/homeassistant/components/stiebel_eltron/climate.py
new file mode 100644
index 00000000000..fc6038d95ad
--- /dev/null
+++ b/homeassistant/components/stiebel_eltron/climate.py
@@ -0,0 +1,149 @@
+"""Support for stiebel_eltron climate platform."""
+import logging
+
+from homeassistant.components.climate import ClimateDevice
+from homeassistant.components.climate.const import (
+ STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
+ SUPPORT_TARGET_TEMPERATURE)
+from homeassistant.const import (
+ ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS)
+
+from . import DOMAIN as STE_DOMAIN
+
+DEPENDENCIES = ['stiebel_eltron']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
+OPERATION_MODES = [STATE_AUTO, STATE_MANUAL, STATE_ECO, STATE_OFF]
+
+# Mapping STIEBEL ELTRON states to homeassistant states.
+STE_TO_HA_STATE = {'AUTOMATIC': STATE_AUTO,
+ 'MANUAL MODE': STATE_MANUAL,
+ 'STANDBY': STATE_ECO,
+ 'DAY MODE': STATE_ON,
+ 'SETBACK MODE': STATE_ON,
+ 'DHW': STATE_OFF,
+ 'EMERGENCY OPERATION': STATE_ON}
+
+# Mapping homeassistant states to STIEBEL ELTRON states.
+HA_TO_STE_STATE = {value: key for key, value in STE_TO_HA_STATE.items()}
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the StiebelEltron platform."""
+ name = hass.data[STE_DOMAIN]['name']
+ ste_data = hass.data[STE_DOMAIN]['ste_data']
+
+ add_entities([StiebelEltron(name, ste_data)], True)
+
+
+class StiebelEltron(ClimateDevice):
+ """Representation of a STIEBEL ELTRON heat pump."""
+
+ def __init__(self, name, ste_data):
+ """Initialize the unit."""
+ self._name = name
+ self._target_temperature = None
+ self._current_temperature = None
+ self._current_humidity = None
+ self._operation_modes = OPERATION_MODES
+ self._current_operation = None
+ self._filter_alarm = None
+ self._force_update = False
+ self._ste_data = ste_data
+
+ @property
+ def supported_features(self):
+ """Return the list of supported features."""
+ return SUPPORT_FLAGS
+
+ def update(self):
+ """Update unit attributes."""
+ self._ste_data.update(no_throttle=self._force_update)
+ self._force_update = False
+
+ self._target_temperature = self._ste_data.api.get_target_temp()
+ self._current_temperature = self._ste_data.api.get_current_temp()
+ self._current_humidity = self._ste_data.api.get_current_humidity()
+ self._filter_alarm = self._ste_data.api.get_filter_alarm_status()
+ self._current_operation = self._ste_data.api.get_operation()
+
+ _LOGGER.debug("Update %s, current temp: %s", self._name,
+ self._current_temperature)
+
+ @property
+ def device_state_attributes(self):
+ """Return device specific state attributes."""
+ return {
+ 'filter_alarm': self._filter_alarm
+ }
+
+ @property
+ def name(self):
+ """Return the name of the climate device."""
+ return self._name
+
+ # Handle SUPPORT_TARGET_TEMPERATURE
+ @property
+ def temperature_unit(self):
+ """Return the unit of measurement."""
+ return TEMP_CELSIUS
+
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ return self._current_temperature
+
+ @property
+ def target_temperature(self):
+ """Return the temperature we try to reach."""
+ return self._target_temperature
+
+ @property
+ def target_temperature_step(self):
+ """Return the supported step of target temperature."""
+ return 0.1
+
+ @property
+ def min_temp(self):
+ """Return the minimum temperature."""
+ return 10.0
+
+ @property
+ def max_temp(self):
+ """Return the maximum temperature."""
+ return 30.0
+
+ def set_temperature(self, **kwargs):
+ """Set new target temperature."""
+ target_temperature = kwargs.get(ATTR_TEMPERATURE)
+ if target_temperature is not None:
+ _LOGGER.debug("set_temperature: %s", target_temperature)
+ self._ste_data.api.set_target_temp(target_temperature)
+ self._force_update = True
+
+ @property
+ def current_humidity(self):
+ """Return the current humidity."""
+ return float("{0:.1f}".format(self._current_humidity))
+
+ # Handle SUPPORT_OPERATION_MODE
+ @property
+ def operation_list(self):
+ """List of the operation modes."""
+ return self._operation_modes
+
+ @property
+ def current_operation(self):
+ """Return current operation ie. heat, cool, idle."""
+ return STE_TO_HA_STATE.get(self._current_operation)
+
+ def set_operation_mode(self, operation_mode):
+ """Set new operation mode."""
+ new_mode = HA_TO_STE_STATE.get(operation_mode)
+ _LOGGER.debug("set_operation_mode: %s -> %s", self._current_operation,
+ new_mode)
+ self._ste_data.api.set_operation(new_mode)
+ self._force_update = True
diff --git a/homeassistant/components/stiebel_eltron/manifest.json b/homeassistant/components/stiebel_eltron/manifest.json
new file mode 100644
index 00000000000..0f8b586a9c2
--- /dev/null
+++ b/homeassistant/components/stiebel_eltron/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "stiebel_eltron",
+ "name": "STIEBEL ELTRON",
+ "documentation": "https://www.home-assistant.io/components/stiebel_eltron",
+ "requirements": [
+ "pystiebeleltron==0.0.1.dev2"
+ ],
+ "dependencies": [
+ "modbus"
+ ],
+ "codeowners": [
+ "@fucm"
+ ]
+}
\ No newline at end of file
diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py
index 1e8ae5d60e3..1de1fc35ea1 100644
--- a/homeassistant/components/stream/__init__.py
+++ b/homeassistant/components/stream/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provide functionality to stream video source.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/stream/
-"""
+"""Provide functionality to stream video source."""
import logging
import threading
@@ -24,12 +19,8 @@ from .worker import stream_worker
from .hls import async_setup_hls
from .recorder import async_setup_recorder
-REQUIREMENTS = ['av==6.1.2']
-
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
-
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py
index aa5ce105764..467e4751208 100644
--- a/homeassistant/components/stream/hls.py
+++ b/homeassistant/components/stream/hls.py
@@ -1,9 +1,4 @@
-"""
-Provide functionality to stream HLS.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/stream/hls
-"""
+"""Provide functionality to stream HLS."""
from aiohttp import web
from homeassistant.core import callback
@@ -90,7 +85,7 @@ class M3U8Renderer:
for sequence in segments:
segment = track.get_segment(sequence)
playlist.extend([
- "#EXTINF:{:.04},".format(float(segment.duration)),
+ "#EXTINF:{:.04f},".format(float(segment.duration)),
"./segment/{}.ts".format(segment.sequence),
])
diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json
new file mode 100644
index 00000000000..9020ffb5b2b
--- /dev/null
+++ b/homeassistant/components/stream/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "stream",
+ "name": "Stream",
+ "documentation": "https://www.home-assistant.io/components/stream",
+ "requirements": [
+ "av==6.1.2"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/stream/services.yaml b/homeassistant/components/stream/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py
index 0292fd30596..d9bc248dc24 100644
--- a/homeassistant/components/stream/worker.py
+++ b/homeassistant/components/stream/worker.py
@@ -55,10 +55,16 @@ def stream_worker(hass, stream, quit_event):
audio_frame = generate_audio_frame()
- outputs = {}
first_packet = True
+ # Holds the buffers for each stream provider
+ outputs = {}
+ # Keep track of the number of segments we've processed
sequence = 1
+ # Holds the generated silence that needs to be muxed into the output
audio_packets = {}
+ # The presentation timestamp of the first video packet we recieve
+ first_pts = 0
+ # The decoder timestamp of the latest packet we processed
last_dts = None
while not quit_event.is_set():
@@ -82,10 +88,18 @@ def stream_worker(hass, stream, quit_event):
continue
last_dts = packet.dts
+ # Reset timestamps from a 0 time base for this stream
+ packet.dts -= first_pts
+ packet.pts -= first_pts
+
# Reset segment on every keyframe
if packet.is_keyframe:
- # Save segment to outputs
+ # Calculate the segment duration by multiplying the presentation
+ # timestamp by the time base, which gets us total seconds.
+ # By then dividing by the seqence, we can calculate how long
+ # each segment is, assuming the stream starts from 0.
segment_duration = (packet.pts * packet.time_base) / sequence
+ # Save segment to outputs
for fmt, buffer in outputs.items():
buffer.output.close()
del audio_packets[buffer.astream]
@@ -112,6 +126,12 @@ def stream_worker(hass, stream, quit_event):
# First video packet tends to have a weird dts/pts
if first_packet:
+ # If we are attaching to a live stream that does not reset
+ # timestamps for us, we need to do it ourselves by recording
+ # the first presentation timestamp and subtracting it from
+ # subsequent packets we recieve.
+ if (packet.pts * packet.time_base) > 1:
+ first_pts = packet.pts
packet.dts = 0
packet.pts = 0
first_packet = False
diff --git a/homeassistant/components/stride/manifest.json b/homeassistant/components/stride/manifest.json
new file mode 100644
index 00000000000..307f4c929cf
--- /dev/null
+++ b/homeassistant/components/stride/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "stride",
+ "name": "Stride",
+ "documentation": "https://www.home-assistant.io/components/stride",
+ "requirements": [
+ "pystride==0.1.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/stride/notify.py b/homeassistant/components/stride/notify.py
index 9d05bd17f34..1ce2cf5e221 100644
--- a/homeassistant/components/stride/notify.py
+++ b/homeassistant/components/stride/notify.py
@@ -1,9 +1,4 @@
-"""
-Stride platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.stride/
-"""
+"""Stride platform for notify component."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET,
PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['pystride==0.1.7']
-
_LOGGER = logging.getLogger(__name__)
CONF_PANEL = 'panel'
diff --git a/homeassistant/components/sun/manifest.json b/homeassistant/components/sun/manifest.json
new file mode 100644
index 00000000000..2ef89da8f69
--- /dev/null
+++ b/homeassistant/components/sun/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "sun",
+ "name": "Sun",
+ "documentation": "https://www.home-assistant.io/components/sun",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/supervisord/manifest.json b/homeassistant/components/supervisord/manifest.json
new file mode 100644
index 00000000000..1fc849165ef
--- /dev/null
+++ b/homeassistant/components/supervisord/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "supervisord",
+ "name": "Supervisord",
+ "documentation": "https://www.home-assistant.io/components/supervisord",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py
index 894881dad86..fc40bd4e867 100644
--- a/homeassistant/components/supervisord/sensor.py
+++ b/homeassistant/components/supervisord/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for Supervisord process status.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.supervisord/
-"""
+"""Sensor for Supervisord process status."""
import logging
import xmlrpc.client
diff --git a/homeassistant/components/supla/__init__.py b/homeassistant/components/supla/__init__.py
new file mode 100644
index 00000000000..127582395e7
--- /dev/null
+++ b/homeassistant/components/supla/__init__.py
@@ -0,0 +1,162 @@
+"""Support for Supla devices."""
+import logging
+from typing import Optional
+
+import voluptuous as vol
+
+from homeassistant.const import CONF_ACCESS_TOKEN
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.discovery import load_platform
+from homeassistant.helpers.entity import Entity
+
+REQUIREMENTS = ['pysupla==0.0.3']
+
+_LOGGER = logging.getLogger(__name__)
+DOMAIN = 'supla'
+
+CONF_SERVER = 'server'
+CONF_SERVERS = 'servers'
+
+SUPLA_FUNCTION_HA_CMP_MAP = {
+ 'CONTROLLINGTHEROLLERSHUTTER': 'cover'
+}
+SUPLA_CHANNELS = 'supla_channels'
+SUPLA_SERVERS = 'supla_servers'
+
+SERVER_CONFIG = vol.Schema({
+ vol.Required(CONF_SERVER): cv.string,
+ vol.Required(CONF_ACCESS_TOKEN): cv.string
+})
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_SERVERS):
+ vol.All(cv.ensure_list, [SERVER_CONFIG])
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, base_config):
+ """Set up the Supla component."""
+ from pysupla import SuplaAPI
+
+ server_confs = base_config[DOMAIN][CONF_SERVERS]
+
+ hass.data[SUPLA_SERVERS] = {}
+ hass.data[SUPLA_CHANNELS] = {}
+
+ for server_conf in server_confs:
+
+ server_address = server_conf[CONF_SERVER]
+
+ server = SuplaAPI(
+ server_address,
+ server_conf[CONF_ACCESS_TOKEN]
+ )
+
+ # Test connection
+ try:
+ srv_info = server.get_server_info()
+ if srv_info.get('authenticated'):
+ hass.data[SUPLA_SERVERS][server_conf[CONF_SERVER]] = server
+ else:
+ _LOGGER.error(
+ 'Server: %s not configured. API call returned: %s',
+ server_address,
+ srv_info
+ )
+ return False
+ except IOError:
+ _LOGGER.exception(
+ 'Server: %s not configured. Error on Supla API access: ',
+ server_address
+ )
+ return False
+
+ discover_devices(hass, base_config)
+
+ return True
+
+
+def discover_devices(hass, hass_config):
+ """
+ Run periodically to discover new devices.
+
+ Currently it's only run at startup.
+ """
+ component_configs = {}
+
+ for server_name, server in hass.data[SUPLA_SERVERS].items():
+
+ for channel in server.get_channels(include=['iodevice']):
+ channel_function = channel['function']['name']
+ component_name = SUPLA_FUNCTION_HA_CMP_MAP.get(channel_function)
+
+ if component_name is None:
+ _LOGGER.warning(
+ 'Unsupported function: %s, channel id: %s',
+ channel_function, channel['id']
+ )
+ continue
+
+ channel['server_name'] = server_name
+ component_configs.setdefault(component_name, []).append(channel)
+
+ # Load discovered devices
+ for component_name, channel in component_configs.items():
+ load_platform(
+ hass,
+ component_name,
+ 'supla',
+ channel,
+ hass_config
+ )
+
+
+class SuplaChannel(Entity):
+ """Base class of a Supla Channel (an equivalent of HA's Entity)."""
+
+ def __init__(self, channel_data):
+ """Channel data -- raw channel information from PySupla."""
+ self.server_name = channel_data['server_name']
+ self.channel_data = channel_data
+
+ @property
+ def server(self):
+ """Return PySupla's server component associated with entity."""
+ return self.hass.data[SUPLA_SERVERS][self.server_name]
+
+ @property
+ def unique_id(self) -> str:
+ """Return a unique ID."""
+ return 'supla-{}-{}'.format(
+ self.channel_data['iodevice']['gUIDString'].lower(),
+ self.channel_data['channelNumber']
+ )
+
+ @property
+ def name(self) -> Optional[str]:
+ """Return the name of the device."""
+ return self.channel_data['caption']
+
+ def action(self, action, **add_pars):
+ """
+ Run server action.
+
+ Actions are currently hardcoded in components.
+ Supla's API enables autodiscovery
+ """
+ _LOGGER.debug(
+ 'Executing action %s on channel %d, params: %s',
+ action,
+ self.channel_data['id'],
+ add_pars
+ )
+ self.server.execute_action(self.channel_data['id'], action, **add_pars)
+
+ def update(self):
+ """Call to update state."""
+ self.channel_data = self.server.get_channel(
+ self.channel_data['id'],
+ include=['connected', 'state']
+ )
diff --git a/homeassistant/components/supla/cover.py b/homeassistant/components/supla/cover.py
new file mode 100644
index 00000000000..c521cf48b94
--- /dev/null
+++ b/homeassistant/components/supla/cover.py
@@ -0,0 +1,57 @@
+"""Support for Supla cover - curtains, rollershutters etc."""
+import logging
+from pprint import pformat
+
+from homeassistant.components.cover import ATTR_POSITION, CoverDevice
+from homeassistant.components.supla import SuplaChannel
+
+DEPENDENCIES = ['supla']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the Supla covers."""
+ if discovery_info is None:
+ return
+
+ _LOGGER.debug('Discovery: %s', pformat(discovery_info))
+
+ add_entities([
+ SuplaCover(device) for device in discovery_info
+ ])
+
+
+class SuplaCover(SuplaChannel, CoverDevice):
+ """Representation of a Supla Cover."""
+
+ @property
+ def current_cover_position(self):
+ """Return current position of cover. 0 is closed, 100 is open."""
+ state = self.channel_data.get('state')
+ if state:
+ return 100 - state['shut']
+ return None
+
+ def set_cover_position(self, **kwargs):
+ """Move the cover to a specific position."""
+ self.action('REVEAL', percentage=kwargs.get(ATTR_POSITION))
+
+ @property
+ def is_closed(self):
+ """Return if the cover is closed."""
+ if self.current_cover_position is None:
+ return None
+ return self.current_cover_position == 0
+
+ def open_cover(self, **kwargs):
+ """Open the cover."""
+ self.action('REVEAL')
+
+ def close_cover(self, **kwargs):
+ """Close the cover."""
+ self.action('SHUT')
+
+ def stop_cover(self, **kwargs):
+ """Stop the cover."""
+ self.action('STOP')
diff --git a/homeassistant/components/supla/manifest.json b/homeassistant/components/supla/manifest.json
new file mode 100644
index 00000000000..cac1a5f18ab
--- /dev/null
+++ b/homeassistant/components/supla/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "supla",
+ "name": "Supla",
+ "documentation": "https://www.home-assistant.io/components/supla",
+ "requirements": [
+ "pysupla==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@mwegrzynek"
+ ]
+}
diff --git a/homeassistant/components/swiss_hydrological_data/manifest.json b/homeassistant/components/swiss_hydrological_data/manifest.json
new file mode 100644
index 00000000000..d6b18d6cba8
--- /dev/null
+++ b/homeassistant/components/swiss_hydrological_data/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "swiss_hydrological_data",
+ "name": "Swiss hydrological data",
+ "documentation": "https://www.home-assistant.io/components/swiss_hydrological_data",
+ "requirements": [
+ "swisshydrodata==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py
index c354ebedb2b..c8a2c62c5bf 100644
--- a/homeassistant/components/swiss_hydrological_data/sensor.py
+++ b/homeassistant/components/swiss_hydrological_data/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for hydrological data from the Federal Office for the Environment FOEN.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.swiss_hydrological_data/
-"""
+"""Support for hydrological data from the Fed. Office for the Environment."""
from datetime import timedelta
import logging
@@ -15,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['swisshydrodata==0.0.3']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by the Swiss Federal Office for the " \
diff --git a/homeassistant/components/swiss_public_transport/manifest.json b/homeassistant/components/swiss_public_transport/manifest.json
new file mode 100644
index 00000000000..99dcdbd0c88
--- /dev/null
+++ b/homeassistant/components/swiss_public_transport/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "swiss_public_transport",
+ "name": "Swiss public transport",
+ "documentation": "https://www.home-assistant.io/components/swiss_public_transport",
+ "requirements": [
+ "python_opendata_transport==0.1.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py
index d9f2410f8ca..9ff5ea71819 100644
--- a/homeassistant/components/swiss_public_transport/sensor.py
+++ b/homeassistant/components/swiss_public_transport/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for transport.opendata.ch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.swiss_public_transport/
-"""
+"""Support for transport.opendata.ch."""
from datetime import timedelta
import logging
@@ -16,8 +11,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['python_opendata_transport==0.1.4']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DEPARTURE_TIME1 = 'next_departure'
diff --git a/homeassistant/components/swisscom/device_tracker.py b/homeassistant/components/swisscom/device_tracker.py
index d5826ecedff..7371762da92 100644
--- a/homeassistant/components/swisscom/device_tracker.py
+++ b/homeassistant/components/swisscom/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Swisscom routers (Internet-Box).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.swisscom/
-"""
+"""Support for Swisscom routers (Internet-Box)."""
import logging
from aiohttp.hdrs import CONTENT_TYPE
diff --git a/homeassistant/components/swisscom/manifest.json b/homeassistant/components/swisscom/manifest.json
new file mode 100644
index 00000000000..e52fda34083
--- /dev/null
+++ b/homeassistant/components/swisscom/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "swisscom",
+ "name": "Swisscom",
+ "documentation": "https://www.home-assistant.io/components/swisscom",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py
index d517f635a92..e3f756abf53 100644
--- a/homeassistant/components/switch/__init__.py
+++ b/homeassistant/components/switch/__init__.py
@@ -1,9 +1,4 @@
-"""
-Component to interface with various switches that can be controlled remotely.
-
-For more details about this component, please refer to the documentation
-at https://home-assistant.io/components/switch/
-"""
+"""Component to interface with switches that can be controlled remotely."""
from datetime import timedelta
import logging
@@ -21,7 +16,6 @@ from homeassistant.const import (
from homeassistant.components import group
DOMAIN = 'switch'
-DEPENDENCIES = ['group']
SCAN_INTERVAL = timedelta(seconds=30)
GROUP_NAME_ALL_SWITCHES = 'all switches'
@@ -39,6 +33,16 @@ PROP_TO_ATTR = {
'today_energy_kwh': ATTR_TODAY_ENERGY_KWH,
}
+DEVICE_CLASS_OUTLET = 'outlet'
+DEVICE_CLASS_SWITCH = 'switch'
+
+DEVICE_CLASSES = [
+ DEVICE_CLASS_OUTLET,
+ DEVICE_CLASS_SWITCH,
+]
+
+DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
+
SWITCH_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
@@ -119,3 +123,8 @@ class SwitchDevice(ToggleEntity):
data[attr] = value
return data
+
+ @property
+ def device_class(self):
+ """Return the class of this device, from component DEVICE_CLASSES."""
+ return None
diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py
index 64f8779e4ab..8f9e489bd9c 100644
--- a/homeassistant/components/switch/light.py
+++ b/homeassistant/components/switch/light.py
@@ -1,9 +1,4 @@
-"""
-Light support for switch entities.
-
-For more information about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.switch/
-"""
+"""Light support for switch entities."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/switch/manifest.json b/homeassistant/components/switch/manifest.json
new file mode 100644
index 00000000000..0f287251582
--- /dev/null
+++ b/homeassistant/components/switch/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "switch",
+ "name": "Switch",
+ "documentation": "https://www.home-assistant.io/components/switch",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json
new file mode 100644
index 00000000000..0143855db37
--- /dev/null
+++ b/homeassistant/components/switchbot/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "switchbot",
+ "name": "Switchbot",
+ "documentation": "https://www.home-assistant.io/components/switchbot",
+ "requirements": [
+ "PySwitchbot==0.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py
index 3db9b5fd226..b8a2a905dcb 100644
--- a/homeassistant/components/switchbot/switch.py
+++ b/homeassistant/components/switchbot/switch.py
@@ -8,8 +8,6 @@ from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_MAC
from homeassistant.helpers.restore_state import RestoreEntity
-REQUIREMENTS = ['PySwitchbot==0.5']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Switchbot'
diff --git a/homeassistant/components/switchmate/manifest.json b/homeassistant/components/switchmate/manifest.json
new file mode 100644
index 00000000000..9461c776d6d
--- /dev/null
+++ b/homeassistant/components/switchmate/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "switchmate",
+ "name": "Switchmate",
+ "documentation": "https://www.home-assistant.io/components/switchmate",
+ "requirements": [
+ "pySwitchmate==0.4.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/switchmate/switch.py b/homeassistant/components/switchmate/switch.py
index 60497e0207b..ed76089147f 100644
--- a/homeassistant/components/switchmate/switch.py
+++ b/homeassistant/components/switchmate/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Switchmate.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.switchmate/
-"""
+"""Support for Switchmate."""
import logging
from datetime import timedelta
@@ -13,8 +8,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_MAC
-REQUIREMENTS = ['pySwitchmate==0.4.5']
-
_LOGGER = logging.getLogger(__name__)
CONF_FLIP_ON_OFF = 'flip_on_off'
diff --git a/homeassistant/components/syncthru/manifest.json b/homeassistant/components/syncthru/manifest.json
new file mode 100644
index 00000000000..1aadeb54909
--- /dev/null
+++ b/homeassistant/components/syncthru/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "syncthru",
+ "name": "Syncthru",
+ "documentation": "https://www.home-assistant.io/components/syncthru",
+ "requirements": [
+ "pysyncthru==0.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py
index 862efb63fd7..33f57fa0371 100644
--- a/homeassistant/components/syncthru/sensor.py
+++ b/homeassistant/components/syncthru/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Samsung Printers with SyncThru web interface.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.syncthru/
-"""
+"""Support for Samsung Printers with SyncThru web interface."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
-REQUIREMENTS = ['pysyncthru==0.3.1']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Samsung Printer'
diff --git a/homeassistant/components/synology/camera.py b/homeassistant/components/synology/camera.py
index b094cf98edf..93647465280 100644
--- a/homeassistant/components/synology/camera.py
+++ b/homeassistant/components/synology/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for Synology Surveillance Station Cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.synology/
-"""
+"""Support for Synology Surveillance Station Cameras."""
import logging
import requests
@@ -19,8 +14,6 @@ from homeassistant.helpers.aiohttp_client import (
async_get_clientsession)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['py-synology==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Synology Camera'
diff --git a/homeassistant/components/synology/manifest.json b/homeassistant/components/synology/manifest.json
new file mode 100644
index 00000000000..a108f5fa983
--- /dev/null
+++ b/homeassistant/components/synology/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "synology",
+ "name": "Synology",
+ "documentation": "https://www.home-assistant.io/components/synology",
+ "requirements": [
+ "py-synology==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/synology_chat/manifest.json b/homeassistant/components/synology_chat/manifest.json
new file mode 100644
index 00000000000..d35b1d8c902
--- /dev/null
+++ b/homeassistant/components/synology_chat/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "synology_chat",
+ "name": "Synology chat",
+ "documentation": "https://www.home-assistant.io/components/synology_chat",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/synology_chat/notify.py b/homeassistant/components/synology_chat/notify.py
index 32277dc1971..8f2f654da3c 100644
--- a/homeassistant/components/synology_chat/notify.py
+++ b/homeassistant/components/synology_chat/notify.py
@@ -1,9 +1,4 @@
-"""
-SynologyChat platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.synology_chat/
-"""
+"""SynologyChat platform for notify component."""
import json
import logging
diff --git a/homeassistant/components/synology_srm/device_tracker.py b/homeassistant/components/synology_srm/device_tracker.py
index bf5653d681b..57dbb7134e2 100644
--- a/homeassistant/components/synology_srm/device_tracker.py
+++ b/homeassistant/components/synology_srm/device_tracker.py
@@ -13,8 +13,6 @@ from homeassistant.const import (
CONF_HOST, CONF_USERNAME, CONF_PASSWORD,
CONF_PORT, CONF_SSL, CONF_VERIFY_SSL)
-REQUIREMENTS = ['synology-srm==0.0.6']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_USERNAME = 'admin'
diff --git a/homeassistant/components/synology_srm/manifest.json b/homeassistant/components/synology_srm/manifest.json
new file mode 100644
index 00000000000..fa89577f26e
--- /dev/null
+++ b/homeassistant/components/synology_srm/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "synology_srm",
+ "name": "Synology srm",
+ "documentation": "https://www.home-assistant.io/components/synology_srm",
+ "requirements": [
+ "synology-srm==0.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@aerialls"
+ ]
+}
diff --git a/homeassistant/components/synologydsm/manifest.json b/homeassistant/components/synologydsm/manifest.json
new file mode 100644
index 00000000000..fcce2e52a21
--- /dev/null
+++ b/homeassistant/components/synologydsm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "synologydsm",
+ "name": "Synologydsm",
+ "documentation": "https://www.home-assistant.io/components/synologydsm",
+ "requirements": [
+ "python-synology==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/synologydsm/sensor.py b/homeassistant/components/synologydsm/sensor.py
index 0d5a253483f..2d12dbfe763 100644
--- a/homeassistant/components/synologydsm/sensor.py
+++ b/homeassistant/components/synologydsm/sensor.py
@@ -13,8 +13,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['python-synology==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Data provided by Synology'
diff --git a/homeassistant/components/syslog/manifest.json b/homeassistant/components/syslog/manifest.json
new file mode 100644
index 00000000000..19836ffa67f
--- /dev/null
+++ b/homeassistant/components/syslog/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "syslog",
+ "name": "Syslog",
+ "documentation": "https://www.home-assistant.io/components/syslog",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/syslog/notify.py b/homeassistant/components/syslog/notify.py
index 740148e28e5..2e6c3bf6123 100644
--- a/homeassistant/components/syslog/notify.py
+++ b/homeassistant/components/syslog/notify.py
@@ -1,9 +1,4 @@
-"""
-Syslog notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.syslog/
-"""
+"""Syslog notification service."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py
index 9a171296ce9..7dbb682b287 100644
--- a/homeassistant/components/system_health/__init__.py
+++ b/homeassistant/components/system_health/__init__.py
@@ -14,7 +14,6 @@ from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
DOMAIN = 'system_health'
INFO_CALLBACK_TIMEOUT = 5
diff --git a/homeassistant/components/system_health/manifest.json b/homeassistant/components/system_health/manifest.json
new file mode 100644
index 00000000000..9c2b7bcae39
--- /dev/null
+++ b/homeassistant/components/system_health/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "system_health",
+ "name": "System health",
+ "documentation": "https://www.home-assistant.io/components/system_health",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py
index d6877c32f0d..c5909309ab3 100644
--- a/homeassistant/components/system_log/__init__.py
+++ b/homeassistant/components/system_log/__init__.py
@@ -20,7 +20,6 @@ CONF_LOGGER = 'logger'
DATA_SYSTEM_LOG = 'system_log'
DEFAULT_MAX_ENTRIES = 50
DEFAULT_FIRE_EVENT = False
-DEPENDENCIES = ['http']
DOMAIN = 'system_log'
EVENT_SYSTEM_LOG = 'system_log_event'
diff --git a/homeassistant/components/system_log/manifest.json b/homeassistant/components/system_log/manifest.json
new file mode 100644
index 00000000000..01f70af4a15
--- /dev/null
+++ b/homeassistant/components/system_log/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "system_log",
+ "name": "System log",
+ "documentation": "https://www.home-assistant.io/components/system_log",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/system_log/services.yaml b/homeassistant/components/system_log/services.yaml
index c168185c9b3..2545d47c825 100644
--- a/homeassistant/components/system_log/services.yaml
+++ b/homeassistant/components/system_log/services.yaml
@@ -1,15 +1,15 @@
-system_log:
- clear:
- description: Clear all log entries.
- write:
- description: Write log entry.
- fields:
- message:
- description: Message to log. [Required]
- example: Something went wrong
- level:
- description: "Log level: debug, info, warning, error, critical. Defaults to 'error'."
- example: debug
- logger:
- description: Logger name under which to log the message. Defaults to 'system_log.external'.
- example: mycomponent.myplatform
+clear:
+ description: Clear all log entries.
+
+write:
+ description: Write log entry.
+ fields:
+ message:
+ description: Message to log. [Required]
+ example: Something went wrong
+ level:
+ description: "Log level: debug, info, warning, error, critical. Defaults to 'error'."
+ example: debug
+ logger:
+ description: Logger name under which to log the message. Defaults to 'system_log.external'.
+ example: mycomponent.myplatform
diff --git a/homeassistant/components/systemmonitor/manifest.json b/homeassistant/components/systemmonitor/manifest.json
new file mode 100644
index 00000000000..591e710a871
--- /dev/null
+++ b/homeassistant/components/systemmonitor/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "systemmonitor",
+ "name": "Systemmonitor",
+ "documentation": "https://www.home-assistant.io/components/systemmonitor",
+ "requirements": [
+ "psutil==5.6.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py
index cf65daa4395..fbd4ed52de7 100644
--- a/homeassistant/components/systemmonitor/sensor.py
+++ b/homeassistant/components/systemmonitor/sensor.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['psutil==5.6.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_ARG = 'arg'
diff --git a/homeassistant/components/sytadin/manifest.json b/homeassistant/components/sytadin/manifest.json
new file mode 100644
index 00000000000..0efc84fc552
--- /dev/null
+++ b/homeassistant/components/sytadin/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "sytadin",
+ "name": "Sytadin",
+ "documentation": "https://www.home-assistant.io/components/sytadin",
+ "requirements": [
+ "beautifulsoup4==4.7.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@gautric"
+ ]
+}
diff --git a/homeassistant/components/sytadin/sensor.py b/homeassistant/components/sytadin/sensor.py
index f8ef18fcffe..887d0800e33 100644
--- a/homeassistant/components/sytadin/sensor.py
+++ b/homeassistant/components/sytadin/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Sytadin Traffic, French Traffic Supervision.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.sytadin/
-"""
+"""Support for Sytadin Traffic, French Traffic Supervision."""
import logging
import re
from datetime import timedelta
@@ -18,8 +13,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['beautifulsoup4==4.7.1']
-
_LOGGER = logging.getLogger(__name__)
URL = 'http://www.sytadin.fr/sys/barometres_de_la_circulation.jsp.html'
diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py
index 8d3f541972e..9bbca925868 100644
--- a/homeassistant/components/tado/__init__.py
+++ b/homeassistant/components/tado/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.util import Throttle
-REQUIREMENTS = ['python-tado==0.2.9']
-
_LOGGER = logging.getLogger(__name__)
DATA_TADO = 'tado_data'
diff --git a/homeassistant/components/tado/manifest.json b/homeassistant/components/tado/manifest.json
new file mode 100644
index 00000000000..8d42cde1c05
--- /dev/null
+++ b/homeassistant/components/tado/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "tado",
+ "name": "Tado",
+ "documentation": "https://www.home-assistant.io/components/tado",
+ "requirements": [
+ "python-tado==0.2.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py
index 1807667da87..9605b9e14e4 100644
--- a/homeassistant/components/tahoma/__init__.py
+++ b/homeassistant/components/tahoma/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['tahoma-api==0.0.14']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'tahoma'
diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py
index 948c6f90a58..f4305077a07 100644
--- a/homeassistant/components/tahoma/binary_sensor.py
+++ b/homeassistant/components/tahoma/binary_sensor.py
@@ -7,8 +7,6 @@ from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON
from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice
-DEPENDENCIES = ['tahoma']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=120)
diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py
index 85e785f9ca3..eeacf7c83b2 100644
--- a/homeassistant/components/tahoma/cover.py
+++ b/homeassistant/components/tahoma/cover.py
@@ -7,8 +7,6 @@ from homeassistant.util.dt import utcnow
from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice
-DEPENDENCIES = ['tahoma']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MEM_POS = 'memorized_position'
diff --git a/homeassistant/components/tahoma/manifest.json b/homeassistant/components/tahoma/manifest.json
new file mode 100644
index 00000000000..ca3ab0bc882
--- /dev/null
+++ b/homeassistant/components/tahoma/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tahoma",
+ "name": "Tahoma",
+ "documentation": "https://www.home-assistant.io/components/tahoma",
+ "requirements": [
+ "tahoma-api==0.0.14"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@philklei"
+ ]
+}
diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py
index eedb95d1a77..cea8217b17a 100644
--- a/homeassistant/components/tahoma/scene.py
+++ b/homeassistant/components/tahoma/scene.py
@@ -5,8 +5,6 @@ from homeassistant.components.scene import Scene
from . import DOMAIN as TAHOMA_DOMAIN
-DEPENDENCIES = ['tahoma']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py
index 3c03911804a..288462dcc80 100644
--- a/homeassistant/components/tahoma/sensor.py
+++ b/homeassistant/components/tahoma/sensor.py
@@ -7,8 +7,6 @@ from homeassistant.helpers.entity import Entity
from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice
-DEPENDENCIES = ['tahoma']
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py
index 71f00ed8937..4877ae61e28 100644
--- a/homeassistant/components/tahoma/switch.py
+++ b/homeassistant/components/tahoma/switch.py
@@ -6,8 +6,6 @@ from homeassistant.const import STATE_OFF, STATE_ON
from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice
-DEPENDENCIES = ['tahoma']
-
_LOGGER = logging.getLogger(__name__)
ATTR_RSSI_LEVEL = 'rssi_level'
diff --git a/homeassistant/components/tank_utility/manifest.json b/homeassistant/components/tank_utility/manifest.json
new file mode 100644
index 00000000000..04ffb48f396
--- /dev/null
+++ b/homeassistant/components/tank_utility/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "tank_utility",
+ "name": "Tank utility",
+ "documentation": "https://www.home-assistant.io/components/tank_utility",
+ "requirements": [
+ "tank_utility==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py
index c807f1aa4c7..8d83b0773ce 100644
--- a/homeassistant/components/tank_utility/sensor.py
+++ b/homeassistant/components/tank_utility/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Tank Utility propane monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.tank_utility/
-"""
+"""Support for the Tank Utility propane monitor."""
import datetime
import logging
@@ -17,10 +12,6 @@ from homeassistant.const import CONF_DEVICES, CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = [
- "tank_utility==1.4.0"
-]
-
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = datetime.timedelta(hours=1)
diff --git a/homeassistant/components/tapsaff/binary_sensor.py b/homeassistant/components/tapsaff/binary_sensor.py
index 1978a127c17..b2875c8e40d 100644
--- a/homeassistant/components/tapsaff/binary_sensor.py
+++ b/homeassistant/components/tapsaff/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Taps Affs.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.tapsaff/
-"""
+"""Support for Taps Affs."""
from datetime import timedelta
import logging
@@ -14,8 +9,6 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['tapsaff==0.2.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_LOCATION = 'location'
diff --git a/homeassistant/components/tapsaff/manifest.json b/homeassistant/components/tapsaff/manifest.json
new file mode 100644
index 00000000000..6008ef38cc6
--- /dev/null
+++ b/homeassistant/components/tapsaff/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "tapsaff",
+ "name": "Tapsaff",
+ "documentation": "https://www.home-assistant.io/components/tapsaff",
+ "requirements": [
+ "tapsaff==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tautulli/manifest.json b/homeassistant/components/tautulli/manifest.json
new file mode 100644
index 00000000000..d49b5280181
--- /dev/null
+++ b/homeassistant/components/tautulli/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tautulli",
+ "name": "Tautulli",
+ "documentation": "https://www.home-assistant.io/components/tautulli",
+ "requirements": [
+ "pytautulli==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py
index 5c48731f7df..ca1651eca68 100644
--- a/homeassistant/components/tautulli/sensor.py
+++ b/homeassistant/components/tautulli/sensor.py
@@ -1,9 +1,4 @@
-"""
-A platform which allows you to get information from Tautulli.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/sensor.tautulli/
-"""
+"""A platform which allows you to get information from Tautulli."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pytautulli==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_MONITORED_USERS = 'monitored_users'
diff --git a/homeassistant/components/tcp/binary_sensor.py b/homeassistant/components/tcp/binary_sensor.py
index 80d77cd52a1..4d26d819ede 100644
--- a/homeassistant/components/tcp/binary_sensor.py
+++ b/homeassistant/components/tcp/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Provides a binary sensor which gets its values from a TCP socket.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.tcp/
-"""
+"""Provides a binary sensor which gets its values from a TCP socket."""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
diff --git a/homeassistant/components/tcp/manifest.json b/homeassistant/components/tcp/manifest.json
new file mode 100644
index 00000000000..2ff29a27f31
--- /dev/null
+++ b/homeassistant/components/tcp/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "tcp",
+ "name": "Tcp",
+ "documentation": "https://www.home-assistant.io/components/tcp",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tcp/sensor.py b/homeassistant/components/tcp/sensor.py
index d214bd3d425..6788848df07 100644
--- a/homeassistant/components/tcp/sensor.py
+++ b/homeassistant/components/tcp/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for TCP socket based sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.tcp/
-"""
+"""Support for TCP socket based sensors."""
import logging
import socket
import select
diff --git a/homeassistant/components/ted5000/manifest.json b/homeassistant/components/ted5000/manifest.json
new file mode 100644
index 00000000000..cf0439345dc
--- /dev/null
+++ b/homeassistant/components/ted5000/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ted5000",
+ "name": "Ted5000",
+ "documentation": "https://www.home-assistant.io/components/ted5000",
+ "requirements": [
+ "xmltodict==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py
index 23a20b3e830..32869949eb9 100644
--- a/homeassistant/components/ted5000/sensor.py
+++ b/homeassistant/components/ted5000/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support gathering ted500 information.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.ted5000/
-"""
+"""Support gathering ted500 information."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['xmltodict==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'ted'
diff --git a/homeassistant/components/teksavvy/manifest.json b/homeassistant/components/teksavvy/manifest.json
new file mode 100644
index 00000000000..14afdec3b71
--- /dev/null
+++ b/homeassistant/components/teksavvy/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "teksavvy",
+ "name": "Teksavvy",
+ "documentation": "https://www.home-assistant.io/components/teksavvy",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/teksavvy/sensor.py b/homeassistant/components/teksavvy/sensor.py
index 0be18cbd6b6..de74ceda9f5 100644
--- a/homeassistant/components/teksavvy/sensor.py
+++ b/homeassistant/components/teksavvy/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for TekSavvy Bandwidth Monitor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.teksavvy/
-"""
+"""Support for TekSavvy Bandwidth Monitor."""
from datetime import timedelta
import logging
import async_timeout
diff --git a/homeassistant/components/telegram/manifest.json b/homeassistant/components/telegram/manifest.json
new file mode 100644
index 00000000000..8a6dd7fb369
--- /dev/null
+++ b/homeassistant/components/telegram/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "telegram",
+ "name": "Telegram",
+ "documentation": "https://www.home-assistant.io/components/telegram",
+ "requirements": [],
+ "dependencies": [
+ "telegram_bot"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/telegram/notify.py b/homeassistant/components/telegram/notify.py
index 428c7e093d2..b18e0e2c1d1 100644
--- a/homeassistant/components/telegram/notify.py
+++ b/homeassistant/components/telegram/notify.py
@@ -1,9 +1,4 @@
-"""
-Telegram platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.telegram/
-"""
+"""Telegram platform for notify component."""
import logging
import voluptuous as vol
@@ -17,8 +12,6 @@ from homeassistant.components.notify import (
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'telegram_bot'
-DEPENDENCIES = [DOMAIN]
-
ATTR_KEYBOARD = 'keyboard'
ATTR_INLINE_KEYBOARD = 'inline_keyboard'
ATTR_PHOTO = 'photo'
diff --git a/homeassistant/components/telegram/services.yaml b/homeassistant/components/telegram/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py
index e8b19ec2b2c..43f8a26644c 100644
--- a/homeassistant/components/telegram_bot/__init__.py
+++ b/homeassistant/components/telegram_bot/__init__.py
@@ -1,6 +1,8 @@
"""Support to send and receive Telegram messages."""
import io
+from ipaddress import ip_network
from functools import partial
+import importlib
import logging
import requests
@@ -11,12 +13,9 @@ from homeassistant.components.notify import (
ATTR_DATA, ATTR_MESSAGE, ATTR_TITLE)
from homeassistant.const import (
ATTR_COMMAND, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_API_KEY,
- CONF_PLATFORM, CONF_TIMEOUT, HTTP_DIGEST_AUTHENTICATION)
+ CONF_PLATFORM, CONF_TIMEOUT, HTTP_DIGEST_AUTHENTICATION, CONF_URL)
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import TemplateError
-from homeassistant.setup import async_prepare_setup_platform
-
-REQUIREMENTS = ['python-telegram-bot==11.1.0']
_LOGGER = logging.getLogger(__name__)
@@ -53,6 +52,7 @@ ATTR_VERIFY_SSL = 'verify_ssl'
CONF_ALLOWED_CHAT_IDS = 'allowed_chat_ids'
CONF_PROXY_URL = 'proxy_url'
CONF_PROXY_PARAMS = 'proxy_params'
+CONF_TRUSTED_NETWORKS = 'trusted_networks'
DOMAIN = 'telegram_bot'
@@ -67,6 +67,7 @@ SERVICE_EDIT_CAPTION = 'edit_caption'
SERVICE_EDIT_REPLYMARKUP = 'edit_replymarkup'
SERVICE_ANSWER_CALLBACK_QUERY = 'answer_callback_query'
SERVICE_DELETE_MESSAGE = 'delete_message'
+SERVICE_LEAVE_CHAT = 'leave_chat'
EVENT_TELEGRAM_CALLBACK = 'telegram_callback'
EVENT_TELEGRAM_COMMAND = 'telegram_command'
@@ -75,17 +76,34 @@ EVENT_TELEGRAM_TEXT = 'telegram_text'
PARSER_HTML = 'html'
PARSER_MD = 'markdown'
-PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
- vol.Required(CONF_PLATFORM): cv.string,
- vol.Required(CONF_API_KEY): cv.string,
- vol.Required(CONF_ALLOWED_CHAT_IDS):
- vol.All(cv.ensure_list, [vol.Coerce(int)]),
- vol.Optional(ATTR_PARSER, default=PARSER_MD): cv.string,
- vol.Optional(CONF_PROXY_URL): cv.string,
- vol.Optional(CONF_PROXY_PARAMS): dict,
-})
+DEFAULT_TRUSTED_NETWORKS = [
+ ip_network('149.154.167.197/32'),
+ ip_network('149.154.167.198/31'),
+ ip_network('149.154.167.200/29'),
+ ip_network('149.154.167.208/28'),
+ ip_network('149.154.167.224/29'),
+ ip_network('149.154.167.232/31')
+]
-PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.All(cv.ensure_list, [
+ vol.Schema({
+ vol.Required(CONF_PLATFORM): vol.In(
+ ('broadcast', 'polling', 'webhooks')),
+ vol.Required(CONF_API_KEY): cv.string,
+ vol.Required(CONF_ALLOWED_CHAT_IDS):
+ vol.All(cv.ensure_list, [vol.Coerce(int)]),
+ vol.Optional(ATTR_PARSER, default=PARSER_MD): cv.string,
+ vol.Optional(CONF_PROXY_URL): cv.string,
+ vol.Optional(CONF_PROXY_PARAMS): dict,
+ # webhooks
+ vol.Optional(CONF_URL): cv.url,
+ vol.Optional(CONF_TRUSTED_NETWORKS,
+ default=DEFAULT_TRUSTED_NETWORKS):
+ vol.All(cv.ensure_list, [ip_network])
+ })
+ ])
+}, extra=vol.ALLOW_EXTRA)
BASE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [vol.Coerce(int)]),
@@ -150,6 +168,10 @@ SERVICE_SCHEMA_DELETE_MESSAGE = vol.Schema({
vol.Any(cv.positive_int, vol.All(cv.string, 'last')),
}, extra=vol.ALLOW_EXTRA)
+SERVICE_SCHEMA_LEAVE_CHAT = vol.Schema({
+ vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
+})
+
SERVICE_MAP = {
SERVICE_SEND_MESSAGE: SERVICE_SCHEMA_SEND_MESSAGE,
SERVICE_SEND_PHOTO: SERVICE_SCHEMA_SEND_FILE,
@@ -162,6 +184,7 @@ SERVICE_MAP = {
SERVICE_EDIT_REPLYMARKUP: SERVICE_SCHEMA_EDIT_REPLYMARKUP,
SERVICE_ANSWER_CALLBACK_QUERY: SERVICE_SCHEMA_ANSWER_CALLBACK_QUERY,
SERVICE_DELETE_MESSAGE: SERVICE_SCHEMA_DELETE_MESSAGE,
+ SERVICE_LEAVE_CHAT: SERVICE_SCHEMA_LEAVE_CHAT,
}
@@ -177,7 +200,7 @@ def load_data(hass, url=None, filepath=None, username=None, password=None,
params["auth"] = HTTPDigestAuth(username, password)
else:
params["auth"] = HTTPBasicAuth(username, password)
- if verify_ssl:
+ if verify_ssl is not None:
params["verify"] = verify_ssl
retry_num = 0
while retry_num < num_retries:
@@ -215,36 +238,33 @@ async def async_setup(hass, config):
if not config[DOMAIN]:
return False
- p_config = config[DOMAIN][0]
+ for p_config in config[DOMAIN]:
- p_type = p_config.get(CONF_PLATFORM)
+ p_type = p_config.get(CONF_PLATFORM)
- platform = await async_prepare_setup_platform(
- hass, config, DOMAIN, p_type)
+ platform = importlib.import_module(
+ '.{}'.format(p_config[CONF_PLATFORM]), __name__)
- if platform is None:
- return
+ _LOGGER.info("Setting up %s.%s", DOMAIN, p_type)
+ try:
+ receiver_service = await \
+ platform.async_setup_platform(hass, p_config)
+ if receiver_service is False:
+ _LOGGER.error(
+ "Failed to initialize Telegram bot %s", p_type)
+ return False
- _LOGGER.info("Setting up %s.%s", DOMAIN, p_type)
- try:
- receiver_service = await \
- platform.async_setup_platform(hass, p_config)
- if receiver_service is False:
- _LOGGER.error(
- "Failed to initialize Telegram bot %s", p_type)
+ except Exception: # pylint: disable=broad-except
+ _LOGGER.exception("Error setting up platform %s", p_type)
return False
- except Exception: # pylint: disable=broad-except
- _LOGGER.exception("Error setting up platform %s", p_type)
- return False
-
- bot = initialize_bot(p_config)
- notify_service = TelegramNotificationService(
- hass,
- bot,
- p_config.get(CONF_ALLOWED_CHAT_IDS),
- p_config.get(ATTR_PARSER)
- )
+ bot = initialize_bot(p_config)
+ notify_service = TelegramNotificationService(
+ hass,
+ bot,
+ p_config.get(CONF_ALLOWED_CHAT_IDS),
+ p_config.get(ATTR_PARSER)
+ )
async def async_send_telegram_message(service):
"""Handle sending Telegram Bot message service calls."""
@@ -566,6 +586,15 @@ class TelegramNotificationService:
chat_id=chat_id,
latitude=latitude, longitude=longitude, **params)
+ def leave_chat(self, chat_id=None):
+ """Remove bot from chat."""
+ chat_id = self._get_target_chat_ids(chat_id)[0]
+ _LOGGER.debug("Leave from chat ID %s", chat_id)
+ leaved = self._send_msg(self.bot.leaveChat,
+ "Error leaving chat",
+ chat_id)
+ return leaved
+
class BaseTelegramBotEntity:
"""The base class for the telegram bot."""
diff --git a/homeassistant/components/telegram_bot/broadcast.py b/homeassistant/components/telegram_bot/broadcast.py
index a129ebf6604..e78c28bd0c4 100644
--- a/homeassistant/components/telegram_bot/broadcast.py
+++ b/homeassistant/components/telegram_bot/broadcast.py
@@ -1,12 +1,10 @@
"""Support for Telegram bot to send messages only."""
import logging
-from . import PLATFORM_SCHEMA as TELEGRAM_PLATFORM_SCHEMA, initialize_bot
+from . import initialize_bot
_LOGGER = logging.getLogger(__name__)
-PLATFORM_SCHEMA = TELEGRAM_PLATFORM_SCHEMA
-
async def async_setup_platform(hass, config):
"""Set up the Telegram broadcast platform."""
diff --git a/homeassistant/components/telegram_bot/manifest.json b/homeassistant/components/telegram_bot/manifest.json
new file mode 100644
index 00000000000..f341fd587ca
--- /dev/null
+++ b/homeassistant/components/telegram_bot/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "telegram_bot",
+ "name": "Telegram bot",
+ "documentation": "https://www.home-assistant.io/components/telegram_bot",
+ "requirements": [
+ "python-telegram-bot==11.1.0"
+ ],
+ "dependencies": ["http"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py
index 7d0039319e3..0d3a8810911 100644
--- a/homeassistant/components/telegram_bot/polling.py
+++ b/homeassistant/components/telegram_bot/polling.py
@@ -5,14 +5,10 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
-from . import (
- CONF_ALLOWED_CHAT_IDS, PLATFORM_SCHEMA as TELEGRAM_PLATFORM_SCHEMA,
- BaseTelegramBotEntity, initialize_bot)
+from . import (CONF_ALLOWED_CHAT_IDS, BaseTelegramBotEntity, initialize_bot)
_LOGGER = logging.getLogger(__name__)
-PLATFORM_SCHEMA = TELEGRAM_PLATFORM_SCHEMA
-
async def async_setup_platform(hass, config):
"""Set up the Telegram polling platform."""
diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py
index 424ece81549..b3b6add0a1c 100644
--- a/homeassistant/components/telegram_bot/webhooks.py
+++ b/homeassistant/components/telegram_bot/webhooks.py
@@ -1,45 +1,20 @@
"""Support for Telegram bots using webhooks."""
import datetime as dt
-from ipaddress import ip_network
import logging
-import voluptuous as vol
-
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.const import KEY_REAL_IP
from homeassistant.const import (
- CONF_URL, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED)
-import homeassistant.helpers.config_validation as cv
+ EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED)
-from . import (
- CONF_ALLOWED_CHAT_IDS, PLATFORM_SCHEMA, BaseTelegramBotEntity,
- initialize_bot)
-
-DEPENDENCIES = ['http']
+from . import (CONF_ALLOWED_CHAT_IDS, CONF_TRUSTED_NETWORKS, CONF_URL,
+ BaseTelegramBotEntity, initialize_bot)
_LOGGER = logging.getLogger(__name__)
TELEGRAM_HANDLER_URL = '/api/telegram_webhooks'
REMOVE_HANDLER_URL = ''
-CONF_TRUSTED_NETWORKS = 'trusted_networks'
-
-DEFAULT_TRUSTED_NETWORKS = [
- ip_network('149.154.167.197/32'),
- ip_network('149.154.167.198/31'),
- ip_network('149.154.167.200/29'),
- ip_network('149.154.167.208/28'),
- ip_network('149.154.167.224/29'),
- ip_network('149.154.167.232/31')
-]
-
-# pylint: disable=no-value-for-parameter
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_URL): vol.Url(),
- vol.Optional(CONF_TRUSTED_NETWORKS, default=DEFAULT_TRUSTED_NETWORKS):
- vol.All(cv.ensure_list, [ip_network])
-})
-
async def async_setup_platform(hass, config):
"""Set up the Telegram webhooks platform."""
diff --git a/homeassistant/components/tellduslive/.translations/es.json b/homeassistant/components/tellduslive/.translations/es.json
index bf1aedab17d..43e3660415e 100644
--- a/homeassistant/components/tellduslive/.translations/es.json
+++ b/homeassistant/components/tellduslive/.translations/es.json
@@ -9,6 +9,17 @@
},
"error": {
"auth_error": "Error de autenticaci\u00f3n, por favor int\u00e9ntalo de nuevo"
+ },
+ "step": {
+ "auth": {
+ "description": "Para vincular tu cuenta de TelldusLivet:\n 1. Pulsa el siguiente enlace\n 2. Inicia sesi\u00f3n en Telldus Live\n 3. Autoriza **{app_name}** (pulsa en **Yes**).\n 4. Vuelve aqu\u00ed y pulsa **ENVIAR**.\n\n [Link TelldusLive account]({auth_url})",
+ "title": "Autenticaci\u00f3n contra TelldusLive"
+ },
+ "user": {
+ "data": {
+ "host": "Host"
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/homeassistant/components/tellduslive/.translations/ko.json b/homeassistant/components/tellduslive/.translations/ko.json
index 8a68e303aff..6b04e867861 100644
--- a/homeassistant/components/tellduslive/.translations/ko.json
+++ b/homeassistant/components/tellduslive/.translations/ko.json
@@ -13,14 +13,14 @@
"step": {
"auth": {
"description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **SUBMIT** \uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})",
- "title": "TelldusLive \uc5d0 \ub300\ud55c \uc778\uc99d"
+ "title": "TelldusLive \uc778\uc99d"
},
"user": {
"data": {
"host": "\ud638\uc2a4\ud2b8"
},
"description": "\uc8c4\uc1a1\ud569\ub2c8\ub2e4. \uad00\ub828 \ub0b4\uc6a9\uc774 \uc544\uc9c1 \uc5c5\ub370\uc774\ud2b8 \ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \ucd94\ud6c4\uc5d0 \ubc18\uc601\ub420 \uc608\uc815\uc774\ub2c8 \uc870\uae08\ub9cc \uae30\ub2e4\ub824\uc8fc\uc138\uc694.",
- "title": "\uc5d4\ub4dc \ud3ec\uc778\ud2b8\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694."
+ "title": "\uc5d4\ub4dc\ud3ec\uc778\ud2b8 \uc120\ud0dd"
}
},
"title": "Telldus Live"
diff --git a/homeassistant/components/tellduslive/.translations/lb.json b/homeassistant/components/tellduslive/.translations/lb.json
index 5eb4d1b978a..4584635066c 100644
--- a/homeassistant/components/tellduslive/.translations/lb.json
+++ b/homeassistant/components/tellduslive/.translations/lb.json
@@ -5,7 +5,7 @@
"already_setup": "TelldusLive ass scho konfigur\u00e9iert",
"authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.",
"authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.",
- "unknown": "Onbekannten Fehler opgetrueden"
+ "unknown": "Onbekannten Feeler opgetrueden"
},
"error": {
"auth_error": "Feeler bei der Authentifikatioun, prob\u00e9iert w.e.g. nach emol"
diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py
index 6a6b18557b0..de665bc314f 100644
--- a/homeassistant/components/tellduslive/__init__.py
+++ b/homeassistant/components/tellduslive/__init__.py
@@ -1,45 +1,32 @@
"""Support for Telldus Live."""
import asyncio
-from functools import partial
import logging
+from functools import partial
import voluptuous as vol
-from homeassistant import config_entries
-from homeassistant.const import CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL, \
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION
import homeassistant.helpers.config_validation as cv
+from homeassistant import config_entries
+from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
-
from . import config_flow # noqa pylint_disable=unused-import
from .const import (
CONF_HOST, DOMAIN, KEY_SCAN_INTERVAL, KEY_SESSION,
MIN_UPDATE_INTERVAL, NOT_SO_PRIVATE_KEY, PUBLIC_KEY, SCAN_INTERVAL,
- SIGNAL_UPDATE_ENTITY, TELLDUS_DISCOVERY_NEW)
+ SIGNAL_UPDATE_ENTITY, TELLDUS_DISCOVERY_NEW
+)
APPLICATION_NAME = 'Home Assistant'
-REQUIREMENTS = ['tellduslive==0.10.10']
-
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(
- vol.Schema({
- vol.Optional(CONF_HOST, default=DOMAIN): cv.string,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)),
- vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
- vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)),
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=SCAN_INTERVAL
- )
- )
+ DOMAIN: vol.Schema({
+ vol.Optional(CONF_HOST, default=DOMAIN): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
+ vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)),
+ })
}, extra=vol.ALLOW_EXTRA)
DATA_CONFIG_ENTRY_LOCK = 'tellduslive_config_entry_lock'
diff --git a/homeassistant/components/tellduslive/binary_sensor.py b/homeassistant/components/tellduslive/binary_sensor.py
index fc13f75838a..1e258b90463 100644
--- a/homeassistant/components/tellduslive/binary_sensor.py
+++ b/homeassistant/components/tellduslive/binary_sensor.py
@@ -10,7 +10,8 @@ from .entry import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/tellduslive/cover.py b/homeassistant/components/tellduslive/cover.py
index 6dac00ed7a2..b2cb5d9e62e 100644
--- a/homeassistant/components/tellduslive/cover.py
+++ b/homeassistant/components/tellduslive/cover.py
@@ -10,7 +10,8 @@ from .entry import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py
index 3847c66b6cb..abbfd8ac92e 100644
--- a/homeassistant/components/tellduslive/light.py
+++ b/homeassistant/components/tellduslive/light.py
@@ -11,7 +11,8 @@ from .entry import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/tellduslive/manifest.json b/homeassistant/components/tellduslive/manifest.json
new file mode 100644
index 00000000000..2e6233f426c
--- /dev/null
+++ b/homeassistant/components/tellduslive/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tellduslive",
+ "name": "Tellduslive",
+ "documentation": "https://www.home-assistant.io/components/tellduslive",
+ "requirements": [
+ "tellduslive==0.10.10"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fredrike"
+ ]
+}
diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py
index 156c11c95a7..8839337590b 100644
--- a/homeassistant/components/tellduslive/sensor.py
+++ b/homeassistant/components/tellduslive/sensor.py
@@ -42,7 +42,8 @@ SENSOR_TYPES = {
}
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/tellduslive/switch.py b/homeassistant/components/tellduslive/switch.py
index 55275b5b754..888feff41f8 100644
--- a/homeassistant/components/tellduslive/switch.py
+++ b/homeassistant/components/tellduslive/switch.py
@@ -10,7 +10,8 @@ from .entry import TelldusLiveEntity
_LOGGER = logging.getLogger(__name__)
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
diff --git a/homeassistant/components/tellstick/__init__.py b/homeassistant/components/tellstick/__init__.py
index c35d2f79027..815e194184b 100644
--- a/homeassistant/components/tellstick/__init__.py
+++ b/homeassistant/components/tellstick/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['tellcore-py==1.1.2', 'tellcore-net==0.4']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DISCOVER_CONFIG = 'config'
diff --git a/homeassistant/components/tellstick/manifest.json b/homeassistant/components/tellstick/manifest.json
new file mode 100644
index 00000000000..c50ba514f2a
--- /dev/null
+++ b/homeassistant/components/tellstick/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "tellstick",
+ "name": "Tellstick",
+ "documentation": "https://www.home-assistant.io/components/tellstick",
+ "requirements": [
+ "tellcore-net==0.4",
+ "tellcore-py==1.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tellstick/sensor.py b/homeassistant/components/tellstick/sensor.py
index 0438ad79abc..39946dac7c1 100644
--- a/homeassistant/components/tellstick/sensor.py
+++ b/homeassistant/components/tellstick/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.const import TEMP_CELSIUS, CONF_ID, CONF_NAME
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-DEPENDENCIES = ['tellstick']
-
_LOGGER = logging.getLogger(__name__)
DatatypeDescription = namedtuple('DatatypeDescription', ['name', 'unit'])
diff --git a/homeassistant/components/telnet/manifest.json b/homeassistant/components/telnet/manifest.json
new file mode 100644
index 00000000000..58f5e15cc1a
--- /dev/null
+++ b/homeassistant/components/telnet/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "telnet",
+ "name": "Telnet",
+ "documentation": "https://www.home-assistant.io/components/telnet",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/telnet/switch.py b/homeassistant/components/telnet/switch.py
index 7c3baf2981a..6ad7e7b43a9 100644
--- a/homeassistant/components/telnet/switch.py
+++ b/homeassistant/components/telnet/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for switch controlled using a telnet connection.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.telnet/
-"""
+"""Support for switch controlled using a telnet connection."""
from datetime import timedelta
import logging
import telnetlib
diff --git a/homeassistant/components/temper/manifest.json b/homeassistant/components/temper/manifest.json
new file mode 100644
index 00000000000..0e60c957d9d
--- /dev/null
+++ b/homeassistant/components/temper/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "temper",
+ "name": "Temper",
+ "documentation": "https://www.home-assistant.io/components/temper",
+ "requirements": [
+ "temperusb==1.5.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py
index 72184df7c8f..9bf6a3296fc 100644
--- a/homeassistant/components/temper/sensor.py
+++ b/homeassistant/components/temper/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for getting temperature from TEMPer devices.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.temper/
-"""
+"""Support for getting temperature from TEMPer devices."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['temperusb==1.5.3']
-
CONF_SCALE = 'scale'
CONF_OFFSET = 'offset'
diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py
index 605ab24a264..bd9c4dfc698 100644
--- a/homeassistant/components/template/binary_sensor.py
+++ b/homeassistant/components/template/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for exposing a templated binary sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.template/
-"""
+"""Support for exposing a templated binary sensor."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py
index 1d3642a6036..2fdcc9f1036 100644
--- a/homeassistant/components/template/cover.py
+++ b/homeassistant/components/template/cover.py
@@ -1,9 +1,4 @@
-"""
-Support for covers which integrate with other components.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/cover.template/
-"""
+"""Support for covers which integrate with other components."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py
index d9182b79a40..cc6505d22f7 100644
--- a/homeassistant/components/template/fan.py
+++ b/homeassistant/components/template/fan.py
@@ -1,9 +1,4 @@
-"""
-Support for Template fans.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/fan.template/
-"""
+"""Support for Template fans."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py
index bf930dd1b38..980f0ad152c 100644
--- a/homeassistant/components/template/light.py
+++ b/homeassistant/components/template/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Template lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.template/
-"""
+"""Support for Template lights."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py
index 527af4c5b85..ad5d0c4aea7 100644
--- a/homeassistant/components/template/lock.py
+++ b/homeassistant/components/template/lock.py
@@ -1,9 +1,4 @@
-"""
-Support for locks which integrates with other components.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/lock.template/
-"""
+"""Support for locks which integrates with other components."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/template/manifest.json b/homeassistant/components/template/manifest.json
new file mode 100644
index 00000000000..c8406c9d084
--- /dev/null
+++ b/homeassistant/components/template/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "template",
+ "name": "Template",
+ "documentation": "https://www.home-assistant.io/components/template",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@PhracturedBlue"
+ ]
+}
diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py
index 5f3af4a06a4..41dc6f8aeeb 100644
--- a/homeassistant/components/template/sensor.py
+++ b/homeassistant/components/template/sensor.py
@@ -1,9 +1,4 @@
-"""
-Allows the creation of a sensor that breaks out state_attributes.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.template/
-"""
+"""Allows the creation of a sensor that breaks out state_attributes."""
import logging
from typing import Optional
diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py
index a2098c2f5fd..541bfd3bcfe 100644
--- a/homeassistant/components/template/switch.py
+++ b/homeassistant/components/template/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for switches which integrates with other components.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.template/
-"""
+"""Support for switches which integrates with other components."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py
index 4e4a80a525e..2125ea80364 100644
--- a/homeassistant/components/tensorflow/image_processing.py
+++ b/homeassistant/components/tensorflow/image_processing.py
@@ -12,8 +12,6 @@ from homeassistant.core import split_entity_id
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['numpy==1.16.2', 'pillow==5.4.1', 'protobuf==3.6.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_MATCHES = 'matches'
diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json
new file mode 100644
index 00000000000..e9643f36b67
--- /dev/null
+++ b/homeassistant/components/tensorflow/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tensorflow",
+ "name": "Tensorflow",
+ "documentation": "https://www.home-assistant.io/components/tensorflow",
+ "requirements": [
+ "numpy==1.16.2",
+ "pillow==5.4.1",
+ "protobuf==3.6.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py
index 244538f5f46..894502aa50a 100644
--- a/homeassistant/components/tesla/__init__.py
+++ b/homeassistant/components/tesla/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
-REQUIREMENTS = ['teslajsonpy==0.0.25']
-
DOMAIN = 'tesla'
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/tesla/binary_sensor.py b/homeassistant/components/tesla/binary_sensor.py
index a87239d2430..147853f5855 100644
--- a/homeassistant/components/tesla/binary_sensor.py
+++ b/homeassistant/components/tesla/binary_sensor.py
@@ -8,8 +8,6 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tesla']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla binary sensor."""
diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py
index 603ce1a4d61..cb2eee4367f 100644
--- a/homeassistant/components/tesla/climate.py
+++ b/homeassistant/components/tesla/climate.py
@@ -11,8 +11,6 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tesla']
-
OPERATION_LIST = [STATE_ON, STATE_OFF]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py
index 5a7693d8370..c3fd649ad4e 100644
--- a/homeassistant/components/tesla/device_tracker.py
+++ b/homeassistant/components/tesla/device_tracker.py
@@ -8,8 +8,6 @@ from . import DOMAIN as TESLA_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tesla']
-
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the Tesla tracker."""
diff --git a/homeassistant/components/tesla/lock.py b/homeassistant/components/tesla/lock.py
index ade394496d6..4601aebf7c7 100644
--- a/homeassistant/components/tesla/lock.py
+++ b/homeassistant/components/tesla/lock.py
@@ -8,8 +8,6 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tesla']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Tesla lock platform."""
diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json
new file mode 100644
index 00000000000..ab32a64e670
--- /dev/null
+++ b/homeassistant/components/tesla/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tesla",
+ "name": "Tesla",
+ "documentation": "https://www.home-assistant.io/components/tesla",
+ "requirements": [
+ "teslajsonpy==0.0.25"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@zabuldon"
+ ]
+}
diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py
index 99705d3f793..1a1fe85e252 100644
--- a/homeassistant/components/tesla/sensor.py
+++ b/homeassistant/components/tesla/sensor.py
@@ -11,8 +11,6 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tesla']
-
SCAN_INTERVAL = timedelta(minutes=5)
diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py
index e00164ff1a7..9b15ca092b4 100644
--- a/homeassistant/components/tesla/switch.py
+++ b/homeassistant/components/tesla/switch.py
@@ -7,7 +7,6 @@ from homeassistant.const import STATE_OFF, STATE_ON
from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tesla']
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/tfiac/climate.py b/homeassistant/components/tfiac/climate.py
index 44fa1909823..c3c42b3b63b 100644
--- a/homeassistant/components/tfiac/climate.py
+++ b/homeassistant/components/tfiac/climate.py
@@ -13,8 +13,6 @@ from homeassistant.components.climate.const import (
from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_FAHRENHEIT
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pytfiac==0.3']
-
SCAN_INTERVAL = timedelta(seconds=60)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/tfiac/manifest.json b/homeassistant/components/tfiac/manifest.json
new file mode 100644
index 00000000000..9997ae00f0a
--- /dev/null
+++ b/homeassistant/components/tfiac/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "tfiac",
+ "name": "Tfiac",
+ "documentation": "https://www.home-assistant.io/components/tfiac",
+ "requirements": [
+ "pytfiac==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fredrike",
+ "@mellado"
+ ]
+}
diff --git a/homeassistant/components/thermoworks_smoke/manifest.json b/homeassistant/components/thermoworks_smoke/manifest.json
new file mode 100644
index 00000000000..fab670627ba
--- /dev/null
+++ b/homeassistant/components/thermoworks_smoke/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "thermoworks_smoke",
+ "name": "Thermoworks smoke",
+ "documentation": "https://www.home-assistant.io/components/thermoworks_smoke",
+ "requirements": [
+ "stringcase==1.2.0",
+ "thermoworks_smoke==0.1.8"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py
index 0c6cddd9fcd..55a4cd67cdd 100644
--- a/homeassistant/components/thermoworks_smoke/sensor.py
+++ b/homeassistant/components/thermoworks_smoke/sensor.py
@@ -17,8 +17,6 @@ from homeassistant.const import TEMP_FAHRENHEIT, CONF_EMAIL, CONF_PASSWORD,\
CONF_MONITORED_CONDITIONS, CONF_EXCLUDE, ATTR_BATTERY_LEVEL
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['thermoworks_smoke==0.1.8', 'stringcase==1.2.0']
-
_LOGGER = logging.getLogger(__name__)
PROBE_1 = 'probe1'
diff --git a/homeassistant/components/thethingsnetwork/manifest.json b/homeassistant/components/thethingsnetwork/manifest.json
new file mode 100644
index 00000000000..8d6082d74bf
--- /dev/null
+++ b/homeassistant/components/thethingsnetwork/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "thethingsnetwork",
+ "name": "Thethingsnetwork",
+ "documentation": "https://www.home-assistant.io/components/thethingsnetwork",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py
index d59b429721b..08cdecf8569 100644
--- a/homeassistant/components/thethingsnetwork/sensor.py
+++ b/homeassistant/components/thethingsnetwork/sensor.py
@@ -22,8 +22,6 @@ ATTR_RAW = 'raw'
ATTR_TIME = 'time'
DEFAULT_TIMEOUT = 10
-DEPENDENCIES = ['thethingsnetwork']
-
CONF_DEVICE_ID = 'device_id'
CONF_VALUES = 'values'
diff --git a/homeassistant/components/thingspeak/__init__.py b/homeassistant/components/thingspeak/__init__.py
index 0fa15e7efb4..d6191dbd300 100644
--- a/homeassistant/components/thingspeak/__init__.py
+++ b/homeassistant/components/thingspeak/__init__.py
@@ -9,8 +9,6 @@ from homeassistant.const import (
from homeassistant.helpers import event, state as state_helper
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['thingspeak==0.4.1']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'thingspeak'
diff --git a/homeassistant/components/thingspeak/manifest.json b/homeassistant/components/thingspeak/manifest.json
new file mode 100644
index 00000000000..482bb94ac2a
--- /dev/null
+++ b/homeassistant/components/thingspeak/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "thingspeak",
+ "name": "Thingspeak",
+ "documentation": "https://www.home-assistant.io/components/thingspeak",
+ "requirements": [
+ "thingspeak==0.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/thinkingcleaner/manifest.json b/homeassistant/components/thinkingcleaner/manifest.json
new file mode 100644
index 00000000000..4e43270a5e0
--- /dev/null
+++ b/homeassistant/components/thinkingcleaner/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "thinkingcleaner",
+ "name": "Thinkingcleaner",
+ "documentation": "https://www.home-assistant.io/components/thinkingcleaner",
+ "requirements": [
+ "pythinkingcleaner==0.0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/thinkingcleaner/sensor.py b/homeassistant/components/thinkingcleaner/sensor.py
index f8462435a45..4f05f142568 100644
--- a/homeassistant/components/thinkingcleaner/sensor.py
+++ b/homeassistant/components/thinkingcleaner/sensor.py
@@ -7,8 +7,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pythinkingcleaner==0.0.3']
-
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
diff --git a/homeassistant/components/thinkingcleaner/switch.py b/homeassistant/components/thinkingcleaner/switch.py
index 38a96eb0298..43b5a8ca422 100644
--- a/homeassistant/components/thinkingcleaner/switch.py
+++ b/homeassistant/components/thinkingcleaner/switch.py
@@ -9,8 +9,6 @@ from homeassistant.helpers.entity import ToggleEntity
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pythinkingcleaner==0.0.3']
-
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
diff --git a/homeassistant/components/thomson/device_tracker.py b/homeassistant/components/thomson/device_tracker.py
index 8a56fcee702..bbce443696d 100644
--- a/homeassistant/components/thomson/device_tracker.py
+++ b/homeassistant/components/thomson/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for THOMSON routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.thomson/
-"""
+"""Support for THOMSON routers."""
import logging
import re
import telnetlib
diff --git a/homeassistant/components/thomson/manifest.json b/homeassistant/components/thomson/manifest.json
new file mode 100644
index 00000000000..063c84d4ff7
--- /dev/null
+++ b/homeassistant/components/thomson/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "thomson",
+ "name": "Thomson",
+ "documentation": "https://www.home-assistant.io/components/thomson",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py
index 0dadf3a61fd..916a5b968b2 100644
--- a/homeassistant/components/threshold/binary_sensor.py
+++ b/homeassistant/components/threshold/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring if a sensor value is below/above a threshold.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.threshold/
-"""
+"""Support for monitoring if a sensor value is below/above a threshold."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/threshold/manifest.json b/homeassistant/components/threshold/manifest.json
new file mode 100644
index 00000000000..107b4351505
--- /dev/null
+++ b/homeassistant/components/threshold/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "threshold",
+ "name": "Threshold",
+ "documentation": "https://www.home-assistant.io/components/threshold",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py
index 19cf6fe6525..15c684b72da 100644
--- a/homeassistant/components/tibber/__init__.py
+++ b/homeassistant/components/tibber/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_ACCESS_TOKEN,
from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-REQUIREMENTS = ['pyTibber==0.10.1']
-
DOMAIN = 'tibber'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json
new file mode 100644
index 00000000000..8c6982f9764
--- /dev/null
+++ b/homeassistant/components/tibber/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tibber",
+ "name": "Tibber",
+ "documentation": "https://www.home-assistant.io/components/tibber",
+ "requirements": [
+ "pyTibber==0.10.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen"
+ ]
+}
diff --git a/homeassistant/components/tikteck/light.py b/homeassistant/components/tikteck/light.py
index 64b4069d98e..d69672cb5fe 100644
--- a/homeassistant/components/tikteck/light.py
+++ b/homeassistant/components/tikteck/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Tikteck lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.tikteck/
-"""
+"""Support for Tikteck lights."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.light import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
-REQUIREMENTS = ['tikteck==0.4']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_TIKTECK_LED = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
diff --git a/homeassistant/components/tikteck/manifest.json b/homeassistant/components/tikteck/manifest.json
new file mode 100644
index 00000000000..7edaf9ba978
--- /dev/null
+++ b/homeassistant/components/tikteck/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "tikteck",
+ "name": "Tikteck",
+ "documentation": "https://www.home-assistant.io/components/tikteck",
+ "requirements": [
+ "tikteck==0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py
index 6da520280e2..f83e4bccea4 100644
--- a/homeassistant/components/tile/device_tracker.py
+++ b/homeassistant/components/tile/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Tile® Bluetooth trackers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.tile/
-"""
+"""Support for Tile® Bluetooth trackers."""
import logging
from datetime import timedelta
@@ -18,8 +13,6 @@ from homeassistant.util import slugify
from homeassistant.util.json import load_json, save_json
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pytile==2.0.6']
-
CLIENT_UUID_CONFIG_FILE = '.tile.conf'
DEVICE_TYPES = ['PHONE', 'TILE']
diff --git a/homeassistant/components/tile/manifest.json b/homeassistant/components/tile/manifest.json
new file mode 100644
index 00000000000..3d26e8315ae
--- /dev/null
+++ b/homeassistant/components/tile/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tile",
+ "name": "Tile",
+ "documentation": "https://www.home-assistant.io/components/tile",
+ "requirements": [
+ "pytile==2.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/time_date/manifest.json b/homeassistant/components/time_date/manifest.json
new file mode 100644
index 00000000000..bd620d4a18f
--- /dev/null
+++ b/homeassistant/components/time_date/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "time_date",
+ "name": "Time date",
+ "documentation": "https://www.home-assistant.io/components/time_date",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py
index 7825867df64..5342dc57692 100644
--- a/homeassistant/components/time_date/sensor.py
+++ b/homeassistant/components/time_date/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for showing the date and the time.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.time_date/
-"""
+"""Support for showing the date and the time."""
from datetime import timedelta
import logging
diff --git a/homeassistant/components/timer/manifest.json b/homeassistant/components/timer/manifest.json
new file mode 100644
index 00000000000..76a506faee8
--- /dev/null
+++ b/homeassistant/components/timer/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "timer",
+ "name": "Timer",
+ "documentation": "https://www.home-assistant.io/components/timer",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tod/manifest.json b/homeassistant/components/tod/manifest.json
new file mode 100644
index 00000000000..ff67748d64c
--- /dev/null
+++ b/homeassistant/components/tod/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "tod",
+ "name": "Tod",
+ "documentation": "https://www.home-assistant.io/components/tod",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py
index a0a3457667f..2ee88080924 100644
--- a/homeassistant/components/todoist/calendar.py
+++ b/homeassistant/components/todoist/calendar.py
@@ -1,9 +1,4 @@
-"""
-Support for Todoist task management (https://todoist.com).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/calendar.todoist/
-"""
+"""Support for Todoist task management (https://todoist.com)."""
from datetime import datetime, timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import Throttle, dt
-REQUIREMENTS = ['todoist-python==7.0.17']
-
_LOGGER = logging.getLogger(__name__)
CONF_EXTRA_PROJECTS = 'custom_projects'
diff --git a/homeassistant/components/todoist/manifest.json b/homeassistant/components/todoist/manifest.json
new file mode 100644
index 00000000000..7a6b4e2efab
--- /dev/null
+++ b/homeassistant/components/todoist/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "todoist",
+ "name": "Todoist",
+ "documentation": "https://www.home-assistant.io/components/todoist",
+ "requirements": [
+ "todoist-python==7.0.17"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/todoist/services.yaml b/homeassistant/components/todoist/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/tof/manifest.json b/homeassistant/components/tof/manifest.json
new file mode 100644
index 00000000000..5f64e661a9a
--- /dev/null
+++ b/homeassistant/components/tof/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tof",
+ "name": "Tof",
+ "documentation": "https://www.home-assistant.io/components/tof",
+ "requirements": [
+ "VL53L1X2==0.1.5"
+ ],
+ "dependencies": [
+ "rpi_gpio"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tof/sensor.py b/homeassistant/components/tof/sensor.py
index a403db03682..66b86da301c 100644
--- a/homeassistant/components/tof/sensor.py
+++ b/homeassistant/components/tof/sensor.py
@@ -12,10 +12,6 @@ from homeassistant.components import rpi_gpio
from homeassistant.const import CONF_NAME
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['VL53L1X2==0.1.5']
-
-DEPENDENCIES = ['rpi_gpio']
-
_LOGGER = logging.getLogger(__name__)
LENGTH_MILLIMETERS = 'mm'
diff --git a/homeassistant/components/tomato/device_tracker.py b/homeassistant/components/tomato/device_tracker.py
index 718adad4212..9d0506fe042 100644
--- a/homeassistant/components/tomato/device_tracker.py
+++ b/homeassistant/components/tomato/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Tomato routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.tomato/
-"""
+"""Support for Tomato routers."""
import json
import logging
import re
diff --git a/homeassistant/components/tomato/manifest.json b/homeassistant/components/tomato/manifest.json
new file mode 100644
index 00000000000..615ea9ecd7e
--- /dev/null
+++ b/homeassistant/components/tomato/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "tomato",
+ "name": "Tomato",
+ "documentation": "https://www.home-assistant.io/components/tomato",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/toon/.translations/es.json b/homeassistant/components/toon/.translations/es.json
new file mode 100644
index 00000000000..db5745ca090
--- /dev/null
+++ b/homeassistant/components/toon/.translations/es.json
@@ -0,0 +1,34 @@
+{
+ "config": {
+ "abort": {
+ "client_id": "El ID de cliente en la configuraci\u00f3n no es v\u00e1lido.",
+ "client_secret": "El secreto de la configuraci\u00f3n no es v\u00e1lido.",
+ "no_agreements": "Esta cuenta no tiene pantallas Toon.",
+ "no_app": "Es necesario configurar Toon antes de poder autenticarse con \u00e9l. [Por favor, lee las instrucciones](https://www.home-assistant.io/components/toon/).",
+ "unknown_auth_fail": "Se ha producido un error inesperado al autenticar."
+ },
+ "error": {
+ "credentials": "Las credenciales proporcionadas no son v\u00e1lidas.",
+ "display_exists": "La pantalla seleccionada ya est\u00e1 configurada."
+ },
+ "step": {
+ "authenticate": {
+ "data": {
+ "password": "Contrase\u00f1a",
+ "tenant": "Inquilino",
+ "username": "Nombre de usuario"
+ },
+ "description": "Identif\u00edcate con tu cuenta de Eneco Toon (no con la cuenta de desarrollador).",
+ "title": "Vincular tu cuenta Toon"
+ },
+ "display": {
+ "data": {
+ "display": "Elige una pantalla"
+ },
+ "description": "Selecciona la pantalla Toon que quieres conectar.",
+ "title": "Seleccionar pantalla"
+ }
+ },
+ "title": "Toon"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/toon/.translations/ko.json b/homeassistant/components/toon/.translations/ko.json
index 3a0698aed8e..dcdf19ca1c3 100644
--- a/homeassistant/components/toon/.translations/ko.json
+++ b/homeassistant/components/toon/.translations/ko.json
@@ -4,7 +4,7 @@
"client_id": "\ud074\ub77c\uc774\uc5b8\ud2b8 ID \uac00 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.",
"client_secret": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ube44\ubc00\ubc88\ud638\uac00 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.",
"no_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.",
- "no_app": "Toon \uc744 \uc778\uc99d\ud558\uae30 \uc804\uc5d0 Toon \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/toon/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694.",
+ "no_app": "Toon \uc744 \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Toon \uc744 \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/toon/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694.",
"unknown_auth_fail": "\uc778\uc99d\ud558\ub294 \ub3d9\uc548 \uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4."
},
"error": {
diff --git a/homeassistant/components/toon/.translations/nn.json b/homeassistant/components/toon/.translations/nn.json
new file mode 100644
index 00000000000..b8dbeff27ca
--- /dev/null
+++ b/homeassistant/components/toon/.translations/nn.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "Toon"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/toon/.translations/pt.json b/homeassistant/components/toon/.translations/pt.json
new file mode 100644
index 00000000000..ebec0df356f
--- /dev/null
+++ b/homeassistant/components/toon/.translations/pt.json
@@ -0,0 +1,12 @@
+{
+ "config": {
+ "step": {
+ "authenticate": {
+ "data": {
+ "password": "Palavra-passe",
+ "username": "Nome de Utilizador"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/toon/.translations/ru.json b/homeassistant/components/toon/.translations/ru.json
index 0cc162218e9..012aa65187c 100644
--- a/homeassistant/components/toon/.translations/ru.json
+++ b/homeassistant/components/toon/.translations/ru.json
@@ -4,7 +4,7 @@
"client_id": "Client ID \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d.",
"client_secret": "Client secret \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d.",
"no_agreements": "\u0423 \u044d\u0442\u043e\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0435\u0442 \u0434\u0438\u0441\u043f\u043b\u0435\u0435\u0432 Toon.",
- "no_app": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Toon \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/toon/).",
+ "no_app": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Toon \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/toon/).",
"unknown_auth_fail": "\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430."
},
"error": {
diff --git a/homeassistant/components/toon/.translations/th.json b/homeassistant/components/toon/.translations/th.json
new file mode 100644
index 00000000000..896d9ba8176
--- /dev/null
+++ b/homeassistant/components/toon/.translations/th.json
@@ -0,0 +1,12 @@
+{
+ "config": {
+ "step": {
+ "authenticate": {
+ "data": {
+ "password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19",
+ "username": "\u0e0a\u0e37\u0e48\u0e2d\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py
index d718b5895e4..da47285934c 100644
--- a/homeassistant/components/toon/__init__.py
+++ b/homeassistant/components/toon/__init__.py
@@ -16,8 +16,6 @@ from .const import (
CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY, CONF_TENANT,
DATA_TOON_CLIENT, DATA_TOON_CONFIG, DOMAIN)
-REQUIREMENTS = ['toonapilib==3.2.2']
-
_LOGGER = logging.getLogger(__name__)
# Validation of the user's configuration
diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py
index 694b7d1d033..c9bec0f3e6a 100644
--- a/homeassistant/components/toon/binary_sensor.py
+++ b/homeassistant/components/toon/binary_sensor.py
@@ -12,8 +12,6 @@ from . import (ToonEntity, ToonDisplayDeviceEntity, ToonBoilerDeviceEntity,
ToonBoilerModuleDeviceEntity)
from .const import DATA_TOON_CLIENT, DOMAIN
-DEPENDENCIES = ['toon']
-
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py
index f09dc010c79..d17cc641db0 100644
--- a/homeassistant/components/toon/climate.py
+++ b/homeassistant/components/toon/climate.py
@@ -15,8 +15,6 @@ from homeassistant.helpers.typing import HomeAssistantType
from . import ToonDisplayDeviceEntity
from .const import DATA_TOON_CLIENT, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN
-DEPENDENCIES = ['toon']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
diff --git a/homeassistant/components/toon/manifest.json b/homeassistant/components/toon/manifest.json
new file mode 100644
index 00000000000..7dbf6768db6
--- /dev/null
+++ b/homeassistant/components/toon/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "toon",
+ "name": "Toon",
+ "documentation": "https://www.home-assistant.io/components/toon",
+ "requirements": [
+ "toonapilib==3.2.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@frenck"
+ ]
+}
diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py
index f58c8ef4840..7762aa0d822 100644
--- a/homeassistant/components/toon/sensor.py
+++ b/homeassistant/components/toon/sensor.py
@@ -11,8 +11,6 @@ from . import (ToonEntity, ToonElectricityMeterDeviceEntity,
from .const import (CURRENCY_EUR, DATA_TOON_CLIENT, DOMAIN, POWER_KWH,
POWER_WATT, VOLUME_CM3, VOLUME_M3, RATIO_PERCENT)
-DEPENDENCIES = ['toon']
-
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
diff --git a/homeassistant/components/torque/manifest.json b/homeassistant/components/torque/manifest.json
new file mode 100644
index 00000000000..9ce41b59861
--- /dev/null
+++ b/homeassistant/components/torque/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "torque",
+ "name": "Torque",
+ "documentation": "https://www.home-assistant.io/components/torque",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py
index 4941633677c..01efd49e862 100644
--- a/homeassistant/components/torque/sensor.py
+++ b/homeassistant/components/torque/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Torque OBD application.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.torque/
-"""
+"""Support for the Torque OBD application."""
import logging
import re
@@ -21,7 +16,6 @@ _LOGGER = logging.getLogger(__name__)
API_PATH = '/api/torque'
DEFAULT_NAME = 'vehicle'
-DEPENDENCIES = ['http']
DOMAIN = 'torque'
ENTITY_NAME_FORMAT = '{0} {1}'
diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py
index a272a22abe5..848202d6ce1 100644
--- a/homeassistant/components/totalconnect/alarm_control_panel.py
+++ b/homeassistant/components/totalconnect/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Interfaces with TotalConnect alarm control panels.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/alarm_control_panel.totalconnect/
-"""
+"""Interfaces with TotalConnect alarm control panels."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from homeassistant.const import (
STATE_ALARM_ARMED_CUSTOM_BYPASS)
-REQUIREMENTS = ['total_connect_client==0.25']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Total Connect'
diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json
new file mode 100644
index 00000000000..adb60599ae5
--- /dev/null
+++ b/homeassistant/components/totalconnect/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "totalconnect",
+ "name": "Totalconnect",
+ "documentation": "https://www.home-assistant.io/components/totalconnect",
+ "requirements": [
+ "total_connect_client==0.25"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/touchline/climate.py b/homeassistant/components/touchline/climate.py
index fa38bd37c8f..e4e4a5b7fb8 100644
--- a/homeassistant/components/touchline/climate.py
+++ b/homeassistant/components/touchline/climate.py
@@ -1,9 +1,4 @@
-"""
-Platform for Roth Touchline heat pump controller.
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/climate.touchline/
-"""
+"""Platform for Roth Touchline heat pump controller."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ from homeassistant.components.climate.const import (
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pytouchline==0.7']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
diff --git a/homeassistant/components/touchline/manifest.json b/homeassistant/components/touchline/manifest.json
new file mode 100644
index 00000000000..5b8b4f521ee
--- /dev/null
+++ b/homeassistant/components/touchline/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "touchline",
+ "name": "Touchline",
+ "documentation": "https://www.home-assistant.io/components/touchline",
+ "requirements": [
+ "pytouchline==0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tplink/.translations/ko.json b/homeassistant/components/tplink/.translations/ko.json
index c31e686a76d..05bebdd1455 100644
--- a/homeassistant/components/tplink/.translations/ko.json
+++ b/homeassistant/components/tplink/.translations/ko.json
@@ -1,12 +1,12 @@
{
"config": {
"abort": {
- "no_devices_found": "TP-Link \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
+ "no_devices_found": "TP-Link \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "\ud558\ub098\uc758 TP-Link \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"step": {
"confirm": {
- "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uc7a5\uce58\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uae30\uae30\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "TP-Link Smart Home"
}
},
diff --git a/homeassistant/components/tplink/.translations/pt.json b/homeassistant/components/tplink/.translations/pt.json
new file mode 100644
index 00000000000..1d9fb41fc8c
--- /dev/null
+++ b/homeassistant/components/tplink/.translations/pt.json
@@ -0,0 +1,10 @@
+{
+ "config": {
+ "step": {
+ "confirm": {
+ "title": "TP-Link Smart Home"
+ }
+ },
+ "title": "TP-Link Smart Home"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/tplink/.translations/th.json b/homeassistant/components/tplink/.translations/th.json
new file mode 100644
index 00000000000..80740c9190f
--- /dev/null
+++ b/homeassistant/components/tplink/.translations/th.json
@@ -0,0 +1,5 @@
+{
+ "config": {
+ "title": "TP-Link Smart Home"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py
index 9fc12db0d63..2ebf342c38d 100644
--- a/homeassistant/components/tplink/__init__.py
+++ b/homeassistant/components/tplink/__init__.py
@@ -33,8 +33,6 @@ CONFIG_SCHEMA = vol.Schema({
}),
}, extra=vol.ALLOW_EXTRA)
-REQUIREMENTS = ['pyHS100==0.3.4']
-
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
diff --git a/homeassistant/components/tplink/device_tracker.py b/homeassistant/components/tplink/device_tracker.py
index 33a5d5f32f8..7b665006a44 100644
--- a/homeassistant/components/tplink/device_tracker.py
+++ b/homeassistant/components/tplink/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for TP-Link routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.tplink/
-"""
+"""Support for TP-Link routers."""
import base64
from datetime import datetime
import hashlib
@@ -22,8 +17,6 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, HTTP_HEADER_X_REQUESTED_WITH)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['tplink==0.2.1']
-
_LOGGER = logging.getLogger(__name__)
HTTP_HEADER_NO_CACHE = 'no-cache'
diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py
index 0ba1dfaa33a..dc2fcce949a 100644
--- a/homeassistant/components/tplink/light.py
+++ b/homeassistant/components/tplink/light.py
@@ -1,9 +1,4 @@
-"""
-Support for TPLink lights.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/light.tplink/
-"""
+"""Support for TPLink lights."""
import logging
import time
@@ -17,8 +12,6 @@ from homeassistant.util.color import (
from . import CONF_LIGHT, DOMAIN as TPLINK_DOMAIN
-DEPENDENCIES = ['tplink']
-
PARALLEL_UPDATES = 0
_LOGGER = logging.getLogger(__name__)
@@ -28,7 +21,8 @@ ATTR_DAILY_ENERGY_KWH = 'daily_energy_kwh'
ATTR_MONTHLY_ENERGY_KWH = 'monthly_energy_kwh'
-def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, add_entities,
+ discovery_info=None):
"""Set up the platform.
Deprecated.
diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json
new file mode 100644
index 00000000000..d164a526fc0
--- /dev/null
+++ b/homeassistant/components/tplink/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "tplink",
+ "name": "Tplink",
+ "documentation": "https://www.home-assistant.io/components/tplink",
+ "requirements": [
+ "pyHS100==0.3.5",
+ "tplink==0.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@rytilahti"
+ ]
+}
diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py
index a75945e9956..a3d680a0a50 100644
--- a/homeassistant/components/tplink/switch.py
+++ b/homeassistant/components/tplink/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for TPLink HS100/HS110/HS200 smart switch.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.tplink/
-"""
+"""Support for TPLink HS100/HS110/HS200 smart switch."""
import logging
import time
@@ -14,8 +9,6 @@ import homeassistant.helpers.device_registry as dr
from . import CONF_SWITCH, DOMAIN as TPLINK_DOMAIN
-DEPENDENCIES = ['tplink']
-
PARALLEL_UPDATES = 0
_LOGGER = logging.getLogger(__name__)
@@ -24,7 +17,8 @@ ATTR_TOTAL_ENERGY_KWH = 'total_energy_kwh'
ATTR_CURRENT_A = 'current_a'
-def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, add_entities,
+ discovery_info=None):
"""Set up the platform.
Deprecated.
diff --git a/homeassistant/components/tplink_lte/__init__.py b/homeassistant/components/tplink_lte/__init__.py
index d0f6e600a0d..d3d2933238d 100644
--- a/homeassistant/components/tplink_lte/__init__.py
+++ b/homeassistant/components/tplink_lte/__init__.py
@@ -6,16 +6,14 @@ import aiohttp
import attr
import voluptuous as vol
-from homeassistant.components.notify import ATTR_TARGET
from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP,
- CONF_RECIPIENT)
+ CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_RECIPIENT,
+ EVENT_HOMEASSISTANT_STOP
+)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.aiohttp_client import async_create_clientsession
-REQUIREMENTS = ['tp-connected==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'tplink_lte'
@@ -23,22 +21,10 @@ DATA_KEY = 'tplink_lte'
CONF_NOTIFY = 'notify'
-# Deprecated in 0.88.0, invalidated in 0.91.0, remove in 0.92.0
-ATTR_TARGET_INVALIDATION_VERSION = '0.91.0'
-
-_NOTIFY_SCHEMA = vol.All(
- vol.Schema({
- vol.Optional(CONF_NAME): cv.string,
- vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]),
- vol.Optional(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string])
- }),
- cv.deprecated(
- ATTR_TARGET,
- replacement_key=CONF_RECIPIENT,
- invalidation_version=ATTR_TARGET_INVALIDATION_VERSION
- ),
- cv.has_at_least_one_key(CONF_RECIPIENT),
-)
+_NOTIFY_SCHEMA = vol.Schema({
+ vol.Optional(CONF_NAME): cv.string,
+ vol.Optional(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string])
+})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
diff --git a/homeassistant/components/tplink_lte/manifest.json b/homeassistant/components/tplink_lte/manifest.json
new file mode 100644
index 00000000000..e3efd8c8331
--- /dev/null
+++ b/homeassistant/components/tplink_lte/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "tplink_lte",
+ "name": "Tplink lte",
+ "documentation": "https://www.home-assistant.io/components/tplink_lte",
+ "requirements": [
+ "tp-connected==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tplink_lte/notify.py b/homeassistant/components/tplink_lte/notify.py
index 519641ed34b..a8844979e5e 100644
--- a/homeassistant/components/tplink_lte/notify.py
+++ b/homeassistant/components/tplink_lte/notify.py
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_RECIPIENT
from ..tplink_lte import DATA_KEY
-DEPENDENCIES = ['tplink_lte']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/traccar/device_tracker.py b/homeassistant/components/traccar/device_tracker.py
index e3ac1427941..1600227bfe2 100644
--- a/homeassistant/components/traccar/device_tracker.py
+++ b/homeassistant/components/traccar/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Traccar device tracking.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.traccar/
-"""
+"""Support for Traccar device tracking."""
from datetime import datetime, timedelta
import logging
@@ -21,8 +16,6 @@ from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import slugify
-REQUIREMENTS = ['pytraccar==0.5.0', 'stringcase==1.2.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
diff --git a/homeassistant/components/traccar/manifest.json b/homeassistant/components/traccar/manifest.json
new file mode 100644
index 00000000000..57bd1383363
--- /dev/null
+++ b/homeassistant/components/traccar/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "traccar",
+ "name": "Traccar",
+ "documentation": "https://www.home-assistant.io/components/traccar",
+ "requirements": [
+ "pytraccar==0.5.0",
+ "stringcase==1.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/trackr/device_tracker.py b/homeassistant/components/trackr/device_tracker.py
index 08d3a4c9445..55f8b7c1faf 100644
--- a/homeassistant/components/trackr/device_tracker.py
+++ b/homeassistant/components/trackr/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for the TrackR platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.trackr/
-"""
+"""Support for the TrackR platform."""
import logging
import voluptuous as vol
@@ -16,8 +11,6 @@ from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pytrackr==0.0.5']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
diff --git a/homeassistant/components/trackr/manifest.json b/homeassistant/components/trackr/manifest.json
new file mode 100644
index 00000000000..6ad348176ba
--- /dev/null
+++ b/homeassistant/components/trackr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "trackr",
+ "name": "Trackr",
+ "documentation": "https://www.home-assistant.io/components/trackr",
+ "requirements": [
+ "pytrackr==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tradfri/.translations/nn.json b/homeassistant/components/tradfri/.translations/nn.json
index b9c68668dac..544604e2b2a 100644
--- a/homeassistant/components/tradfri/.translations/nn.json
+++ b/homeassistant/components/tradfri/.translations/nn.json
@@ -5,7 +5,7 @@
},
"error": {
"cannot_connect": "Klarte ikkje \u00e5 kople til gatewayen.",
- "invalid_key": "Kunne ikkje registrere med den brukte n\u00f8kkelen. Dersom dette held fram, pr\u00f8v \u00e5 starte gatewayen p\u00e5 nytt. ",
+ "invalid_key": "Kunne ikkje registrere med den brukte n\u00f8kkelen. Dersom dette held fram, pr\u00f8v \u00e5 starta gatewayen p\u00e5 ny.",
"timeout": "Tida gjekk ut for validering av kode"
},
"step": {
diff --git a/homeassistant/components/tradfri/.translations/ru.json b/homeassistant/components/tradfri/.translations/ru.json
index 352579f810c..e1e0c950618 100644
--- a/homeassistant/components/tradfri/.translations/ru.json
+++ b/homeassistant/components/tradfri/.translations/ru.json
@@ -15,7 +15,7 @@
"security_code": "\u041a\u043e\u0434 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438"
},
"description": "\u041a\u043e\u0434 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u0430 \u0437\u0430\u0434\u043d\u0435\u0439 \u043f\u0430\u043d\u0435\u043b\u0438 \u0448\u043b\u044e\u0437\u0430.",
- "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 "
+ "title": "IKEA TR\u00c5DFRI"
}
},
"title": "IKEA TR\u00c5DFRI"
diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py
index b14bc811754..ec339bc6312 100644
--- a/homeassistant/components/tradfri/__init__.py
+++ b/homeassistant/components/tradfri/__init__.py
@@ -13,8 +13,6 @@ from .const import (
from . import config_flow # noqa pylint_disable=unused-import
-REQUIREMENTS = ['pytradfri[async]==6.0.1']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py
index 38ce428b51b..a2b2cdc7c49 100644
--- a/homeassistant/components/tradfri/light.py
+++ b/homeassistant/components/tradfri/light.py
@@ -17,7 +17,6 @@ ATTR_DIMMER = 'dimmer'
ATTR_HUE = 'hue'
ATTR_SAT = 'saturation'
ATTR_TRANSITION_TIME = 'transition_time'
-DEPENDENCIES = ['tradfri']
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA
IKEA = 'IKEA of Sweden'
TRADFRI_LIGHT_MANAGER = 'Tradfri Light Manager'
@@ -263,8 +262,6 @@ class TradfriLight(Light):
brightness = kwargs[ATTR_BRIGHTNESS]
if brightness > 254:
brightness = 254
- elif brightness < 0:
- brightness = 0
dimmer_data = {ATTR_DIMMER: brightness, ATTR_TRANSITION_TIME:
transition_time}
dimmer_command = self._light_control.set_dimmer(**dimmer_data)
diff --git a/homeassistant/components/tradfri/manifest.json b/homeassistant/components/tradfri/manifest.json
new file mode 100644
index 00000000000..19e8348e987
--- /dev/null
+++ b/homeassistant/components/tradfri/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "tradfri",
+ "name": "Tradfri",
+ "documentation": "https://www.home-assistant.io/components/tradfri",
+ "requirements": [
+ "pytradfri[async]==6.0.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ggravlingen"
+ ]
+}
diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py
index acc84a93590..b6f4aef370d 100644
--- a/homeassistant/components/tradfri/sensor.py
+++ b/homeassistant/components/tradfri/sensor.py
@@ -9,8 +9,6 @@ from . import KEY_API, KEY_GATEWAY
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tradfri']
-
SCAN_INTERVAL = timedelta(minutes=5)
diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py
index ef9a9537cff..b7826624f52 100644
--- a/homeassistant/components/tradfri/switch.py
+++ b/homeassistant/components/tradfri/switch.py
@@ -9,7 +9,6 @@ from .const import CONF_GATEWAY_ID
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['tradfri']
IKEA = 'IKEA of Sweden'
TRADFRI_SWITCH_MANAGER = 'Tradfri Switch Manager'
diff --git a/homeassistant/components/trafikverket_weatherstation/manifest.json b/homeassistant/components/trafikverket_weatherstation/manifest.json
new file mode 100644
index 00000000000..9bd734fe094
--- /dev/null
+++ b/homeassistant/components/trafikverket_weatherstation/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "trafikverket_weatherstation",
+ "name": "Trafikverket weatherstation",
+ "documentation": "https://www.home-assistant.io/components/trafikverket_weatherstation",
+ "requirements": [
+ "pytrafikverket==0.1.5.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py
index 2dffd580b7e..c846d020c84 100644
--- a/homeassistant/components/trafikverket_weatherstation/sensor.py
+++ b/homeassistant/components/trafikverket_weatherstation/sensor.py
@@ -1,9 +1,4 @@
-"""
-Weather information for air and road temperature, provided by Trafikverket.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.trafikverket_weatherstation/
-"""
+"""Weather information for air and road temperature (by Trafikverket)."""
import asyncio
from datetime import timedelta
@@ -21,8 +16,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pytrafikverket==0.1.5.9']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by Trafikverket"
diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py
index 25e21dc3d8a..5a2fbbff5cb 100644
--- a/homeassistant/components/transmission/__init__.py
+++ b/homeassistant/components/transmission/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['transmissionrpc==0.11']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'transmission'
diff --git a/homeassistant/components/transmission/manifest.json b/homeassistant/components/transmission/manifest.json
new file mode 100644
index 00000000000..bc5da64fcac
--- /dev/null
+++ b/homeassistant/components/transmission/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "transmission",
+ "name": "Transmission",
+ "documentation": "https://www.home-assistant.io/components/transmission",
+ "requirements": [
+ "transmissionrpc==0.11"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py
index dfd4c195097..ac3bb3b2626 100644
--- a/homeassistant/components/transmission/sensor.py
+++ b/homeassistant/components/transmission/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.helpers.entity import Entity
from . import DATA_TRANSMISSION, DATA_UPDATED, SENSOR_TYPES
-DEPENDENCIES = ['transmission']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Transmission'
diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py
index 854a2e727b0..bd965e172b1 100644
--- a/homeassistant/components/transmission/switch.py
+++ b/homeassistant/components/transmission/switch.py
@@ -8,8 +8,6 @@ from homeassistant.helpers.entity import ToggleEntity
from . import DATA_TRANSMISSION, DATA_UPDATED
-DEPENDENCIES = ['transmission']
-
_LOGGING = logging.getLogger(__name__)
DEFAULT_NAME = 'Transmission Turtle Mode'
diff --git a/homeassistant/components/transport_nsw/manifest.json b/homeassistant/components/transport_nsw/manifest.json
new file mode 100644
index 00000000000..491cce7407f
--- /dev/null
+++ b/homeassistant/components/transport_nsw/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "transport_nsw",
+ "name": "Transport nsw",
+ "documentation": "https://www.home-assistant.io/components/transport_nsw",
+ "requirements": [
+ "PyTransportNSW==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py
index 3c40bf4f709..9549814e002 100644
--- a/homeassistant/components/transport_nsw/sensor.py
+++ b/homeassistant/components/transport_nsw/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_API_KEY, ATTR_ATTRIBUTION)
-REQUIREMENTS = ['PyTransportNSW==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
ATTR_STOP_ID = 'stop_id'
diff --git a/homeassistant/components/travisci/manifest.json b/homeassistant/components/travisci/manifest.json
new file mode 100644
index 00000000000..eb553fbe73c
--- /dev/null
+++ b/homeassistant/components/travisci/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "travisci",
+ "name": "Travisci",
+ "documentation": "https://www.home-assistant.io/components/travisci",
+ "requirements": [
+ "TravisPy==0.3.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/travisci/sensor.py b/homeassistant/components/travisci/sensor.py
index c96bb18e958..7d94e9e910e 100644
--- a/homeassistant/components/travisci/sensor.py
+++ b/homeassistant/components/travisci/sensor.py
@@ -1,9 +1,4 @@
-"""
-This component provides HA sensor support for Travis CI framework.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.travisci/
-"""
+"""This component provides HA sensor support for Travis CI framework."""
import logging
from datetime import timedelta
@@ -16,8 +11,6 @@ from homeassistant.const import (
CONF_MONITORED_CONDITIONS)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['TravisPy==0.3.5']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Information provided by https://travis-ci.org/"
diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py
index fe3d10ab72c..a7fb18bf5b7 100644
--- a/homeassistant/components/trend/binary_sensor.py
+++ b/homeassistant/components/trend/binary_sensor.py
@@ -10,15 +10,13 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, CONF_DEVICE_CLASS, CONF_ENTITY_ID,
- CONF_FRIENDLY_NAME, STATE_UNKNOWN, CONF_SENSORS)
+ CONF_FRIENDLY_NAME, STATE_UNKNOWN, STATE_UNAVAILABLE, CONF_SENSORS)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util import utcnow
-REQUIREMENTS = ['numpy==1.16.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ATTRIBUTE = 'attribute'
@@ -140,8 +138,8 @@ class SensorTrend(BinarySensorDevice):
state = new_state.attributes.get(self._attribute)
else:
state = new_state.state
- if state != STATE_UNKNOWN:
- sample = (utcnow().timestamp(), float(state))
+ if state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
+ sample = (new_state.last_updated.timestamp(), float(state))
self.samples.append(sample)
self.async_schedule_update_ha_state(True)
except (ValueError, TypeError) as ex:
diff --git a/homeassistant/components/trend/manifest.json b/homeassistant/components/trend/manifest.json
new file mode 100644
index 00000000000..865d7064db2
--- /dev/null
+++ b/homeassistant/components/trend/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "trend",
+ "name": "Trend",
+ "documentation": "https://www.home-assistant.io/components/trend",
+ "requirements": [
+ "numpy==1.16.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py
index 0cd4a1bb6c6..8af22fbb460 100644
--- a/homeassistant/components/tts/__init__.py
+++ b/homeassistant/components/tts/__init__.py
@@ -1,9 +1,4 @@
-"""
-Provide functionality to TTS.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts/
-"""
+"""Provide functionality to TTS."""
import asyncio
import ctypes
import functools as ft
@@ -22,15 +17,13 @@ from homeassistant.components.media_player.const import (
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, MEDIA_TYPE_MUSIC,
SERVICE_PLAY_MEDIA)
from homeassistant.components.media_player.const import DOMAIN as DOMAIN_MP
-from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL
+from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, CONF_PLATFORM
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.setup import async_prepare_setup_platform
-REQUIREMENTS = ['mutagen==1.42.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CACHE = 'cache'
@@ -39,16 +32,16 @@ ATTR_MESSAGE = 'message'
ATTR_OPTIONS = 'options'
ATTR_PLATFORM = 'platform'
+CONF_BASE_URL = 'base_url'
CONF_CACHE = 'cache'
CONF_CACHE_DIR = 'cache_dir'
CONF_LANG = 'language'
+CONF_SERVICE_NAME = 'service_name'
CONF_TIME_MEMORY = 'time_memory'
-CONF_BASE_URL = 'base_url'
DEFAULT_CACHE = True
DEFAULT_CACHE_DIR = 'tts'
DEFAULT_TIME_MEMORY = 300
-DEPENDENCIES = ['http']
DOMAIN = 'tts'
MEM_CACHE_FILENAME = 'filename'
@@ -61,12 +54,24 @@ _RE_VOICE_FILE = re.compile(
r"([a-f0-9]{40})_([^_]+)_([^_]+)_([a-z_]+)\.[a-z0-9]{3,4}")
KEY_PATTERN = '{0}_{1}_{2}_{3}'
+
+def _deprecated_platform(value):
+ """Validate if platform is deprecated."""
+ if value == 'google':
+ raise vol.Invalid(
+ 'google tts service has been renamed to google_translate,'
+ ' please update your configuration.')
+ return value
+
+
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_PLATFORM): vol.All(cv.string, _deprecated_platform),
vol.Optional(CONF_CACHE, default=DEFAULT_CACHE): cv.boolean,
vol.Optional(CONF_CACHE_DIR, default=DEFAULT_CACHE_DIR): cv.string,
vol.Optional(CONF_TIME_MEMORY, default=DEFAULT_TIME_MEMORY):
vol.All(vol.Coerce(int), vol.Range(min=60, max=57600)),
vol.Optional(CONF_BASE_URL): cv.string,
+ vol.Optional(CONF_SERVICE_NAME): cv.string,
})
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE.extend(PLATFORM_SCHEMA.schema)
@@ -150,8 +155,10 @@ async def async_setup(hass, config):
await hass.services.async_call(
DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True)
+ service_name = p_config.get(CONF_SERVICE_NAME, "{}_{}".format(
+ p_type, SERVICE_SAY))
hass.services.async_register(
- DOMAIN, "{}_{}".format(p_type, SERVICE_SAY), async_say_handle,
+ DOMAIN, service_name, async_say_handle,
schema=SCHEMA_SERVICE_SAY)
setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config
diff --git a/homeassistant/components/tts/manifest.json b/homeassistant/components/tts/manifest.json
new file mode 100644
index 00000000000..ce600473cc5
--- /dev/null
+++ b/homeassistant/components/tts/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "tts",
+ "name": "Tts",
+ "documentation": "https://www.home-assistant.io/components/tts",
+ "requirements": [
+ "mutagen==1.42.0"
+ ],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py
index 117424fd55e..6f6b05100ec 100644
--- a/homeassistant/components/tuya/__init__.py
+++ b/homeassistant/components/tuya/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['tuyapy==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
CONF_COUNTRYCODE = 'country_code'
diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py
index b7a10dad862..b6fd3be04ed 100644
--- a/homeassistant/components/tuya/climate.py
+++ b/homeassistant/components/tuya/climate.py
@@ -10,7 +10,6 @@ from homeassistant.const import (
from . import DATA_TUYA, TuyaDevice
-DEPENDENCIES = ['tuya']
DEVICE_TYPE = 'climate'
HA_STATE_TO_TUYA = {
diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py
index 274f4d93869..6d43365e808 100644
--- a/homeassistant/components/tuya/cover.py
+++ b/homeassistant/components/tuya/cover.py
@@ -4,8 +4,6 @@ from homeassistant.components.cover import (
from . import DATA_TUYA, TuyaDevice
-DEPENDENCIES = ['tuya']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Tuya cover devices."""
diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py
index 259417869dc..897a82716af 100644
--- a/homeassistant/components/tuya/fan.py
+++ b/homeassistant/components/tuya/fan.py
@@ -5,8 +5,6 @@ from homeassistant.const import STATE_OFF
from . import DATA_TUYA, TuyaDevice
-DEPENDENCIES = ['tuya']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Tuya fan platform."""
diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py
index 17f9b43dcbe..cb3f82234d3 100644
--- a/homeassistant/components/tuya/light.py
+++ b/homeassistant/components/tuya/light.py
@@ -6,8 +6,6 @@ from homeassistant.util import color as colorutil
from . import DATA_TUYA, TuyaDevice
-DEPENDENCIES = ['tuya']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Tuya light platform."""
diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json
new file mode 100644
index 00000000000..f4361c89909
--- /dev/null
+++ b/homeassistant/components/tuya/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "tuya",
+ "name": "Tuya",
+ "documentation": "https://www.home-assistant.io/components/tuya",
+ "requirements": [
+ "tuyapy==0.1.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/tuya/scene.py b/homeassistant/components/tuya/scene.py
index 24383dca6e4..6a8fd9d41aa 100644
--- a/homeassistant/components/tuya/scene.py
+++ b/homeassistant/components/tuya/scene.py
@@ -3,8 +3,6 @@ from homeassistant.components.scene import DOMAIN, Scene
from . import DATA_TUYA, TuyaDevice
-DEPENDENCIES = ['tuya']
-
ENTITY_ID_FORMAT = DOMAIN + '.{}'
diff --git a/homeassistant/components/tuya/services.yaml b/homeassistant/components/tuya/services.yaml
new file mode 100644
index 00000000000..c96ea3fd09f
--- /dev/null
+++ b/homeassistant/components/tuya/services.yaml
@@ -0,0 +1,7 @@
+# Describes the format for available Tuya services
+
+pull_devices:
+ description: Pull device list from Tuya server.
+
+force_update:
+ description: Force all Tuya devices to pull data.
diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py
index c2e32eedc59..05b023a78ad 100644
--- a/homeassistant/components/tuya/switch.py
+++ b/homeassistant/components/tuya/switch.py
@@ -3,8 +3,6 @@ from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
from . import DATA_TUYA, TuyaDevice
-DEPENDENCIES = ['tuya']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Tuya Switch device."""
diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py
index ce8c272165f..82011f499ba 100644
--- a/homeassistant/components/twilio/__init__.py
+++ b/homeassistant/components/twilio/__init__.py
@@ -5,9 +5,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow
-REQUIREMENTS = ['twilio==6.19.1']
-DEPENDENCIES = ['webhook']
-
DOMAIN = 'twilio'
CONF_ACCOUNT_SID = 'account_sid'
@@ -60,6 +57,11 @@ async def async_unload_entry(hass, entry):
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True
+
+# pylint: disable=invalid-name
+async_remove_entry = config_entry_flow.webhook_async_remove_entry
+
+
config_entry_flow.register_webhook_flow(
DOMAIN,
'Twilio Webhook',
diff --git a/homeassistant/components/twilio/manifest.json b/homeassistant/components/twilio/manifest.json
new file mode 100644
index 00000000000..dfb7dd4b14d
--- /dev/null
+++ b/homeassistant/components/twilio/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "twilio",
+ "name": "Twilio",
+ "documentation": "https://www.home-assistant.io/components/twilio",
+ "requirements": [
+ "twilio==6.19.1"
+ ],
+ "dependencies": [
+ "webhook"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/twilio_call/manifest.json b/homeassistant/components/twilio_call/manifest.json
new file mode 100644
index 00000000000..b235385396b
--- /dev/null
+++ b/homeassistant/components/twilio_call/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "twilio_call",
+ "name": "Twilio call",
+ "documentation": "https://www.home-assistant.io/components/twilio_call",
+ "requirements": [],
+ "dependencies": [
+ "twilio"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/twilio_call/notify.py b/homeassistant/components/twilio_call/notify.py
index a1a28a03b18..0387ad31cb6 100644
--- a/homeassistant/components/twilio_call/notify.py
+++ b/homeassistant/components/twilio_call/notify.py
@@ -1,9 +1,4 @@
-"""
-Twilio Call platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.twilio_call/
-"""
+"""Twilio Call platform for notify component."""
import logging
import urllib
@@ -17,8 +12,6 @@ from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['twilio']
-
CONF_FROM_NUMBER = 'from_number'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/twilio_sms/manifest.json b/homeassistant/components/twilio_sms/manifest.json
new file mode 100644
index 00000000000..2174dc275b5
--- /dev/null
+++ b/homeassistant/components/twilio_sms/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "twilio_sms",
+ "name": "Twilio sms",
+ "documentation": "https://www.home-assistant.io/components/twilio_sms",
+ "requirements": [],
+ "dependencies": [
+ "twilio"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/twilio_sms/notify.py b/homeassistant/components/twilio_sms/notify.py
index b3b35ea1789..6ac6d085de5 100644
--- a/homeassistant/components/twilio_sms/notify.py
+++ b/homeassistant/components/twilio_sms/notify.py
@@ -1,9 +1,4 @@
-"""
-Twilio SMS platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.twilio_sms/
-"""
+"""Twilio SMS platform for notify component."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA,
BaseNotificationService)
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ["twilio"]
-
CONF_FROM_NUMBER = "from_number"
diff --git a/homeassistant/components/twitch/manifest.json b/homeassistant/components/twitch/manifest.json
new file mode 100644
index 00000000000..80bc795b536
--- /dev/null
+++ b/homeassistant/components/twitch/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "twitch",
+ "name": "Twitch",
+ "documentation": "https://www.home-assistant.io/components/twitch",
+ "requirements": [
+ "python-twitch-client==0.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py
index 3e00f799dcf..e5223b13b01 100644
--- a/homeassistant/components/twitch/sensor.py
+++ b/homeassistant/components/twitch/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Twitch stream status.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.twitch/
-"""
+"""Support for the Twitch stream status."""
import logging
import voluptuous as vol
@@ -12,8 +7,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-twitch-client==0.6.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_GAME = 'game'
diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json
new file mode 100644
index 00000000000..e721bb669ed
--- /dev/null
+++ b/homeassistant/components/twitter/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "twitter",
+ "name": "Twitter",
+ "documentation": "https://www.home-assistant.io/components/twitter",
+ "requirements": [
+ "TwitterAPI==2.5.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/twitter/notify.py b/homeassistant/components/twitter/notify.py
index 9172da36785..305fec7269d 100644
--- a/homeassistant/components/twitter/notify.py
+++ b/homeassistant/components/twitter/notify.py
@@ -1,9 +1,4 @@
-"""
-Twitter platform for notify component.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.twitter/
-"""
+"""Twitter platform for notify component."""
from datetime import datetime, timedelta
from functools import partial
import json
@@ -20,8 +15,6 @@ from homeassistant.helpers.event import async_track_point_in_time
from homeassistant.components.notify import (ATTR_DATA, PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['TwitterAPI==2.5.9']
-
_LOGGER = logging.getLogger(__name__)
CONF_CONSUMER_KEY = 'consumer_key'
diff --git a/homeassistant/components/ubee/device_tracker.py b/homeassistant/components/ubee/device_tracker.py
index f4ecc7d4855..8e610a4f51c 100644
--- a/homeassistant/components/ubee/device_tracker.py
+++ b/homeassistant/components/ubee/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Ubee router.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.ubee/
-"""
+"""Support for Ubee router."""
import logging
import voluptuous as vol
@@ -14,79 +9,58 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyubee==0.2']
-
_LOGGER = logging.getLogger(__name__)
+CONF_MODEL = 'model'
+DEFAULT_MODEL = 'detect'
+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
+ vol.Optional(CONF_MODEL, default=DEFAULT_MODEL): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return a Ubee scanner."""
- try:
- return UbeeDeviceScanner(config[DOMAIN])
- except ConnectionError:
+ info = config[DOMAIN]
+ host = info[CONF_HOST]
+ username = info[CONF_USERNAME]
+ password = info[CONF_PASSWORD]
+ model = info[CONF_MODEL]
+
+ from pyubee import Ubee
+ ubee = Ubee(host, username, password, model)
+ if not ubee.login():
+ _LOGGER.error("Login failed")
return None
+ scanner = UbeeDeviceScanner(ubee)
+ return scanner
+
class UbeeDeviceScanner(DeviceScanner):
"""This class queries a wireless Ubee router."""
- def __init__(self, config):
+ def __init__(self, ubee):
"""Initialize the Ubee scanner."""
- from pyubee import Ubee
-
- self.host = config[CONF_HOST]
- self.username = config[CONF_USERNAME]
- self.password = config[CONF_PASSWORD]
-
- self.last_results = {}
- self.mac2name = {}
-
- self.ubee = Ubee(self.host, self.username, self.password)
- _LOGGER.info("Logging in")
- results = self.get_connected_devices()
- self.success_init = results is not None
-
- if self.success_init:
- self.last_results = results
- else:
- _LOGGER.error("Login failed")
+ self._ubee = ubee
+ self._mac2name = {}
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
- self._update_info()
-
- return self.last_results
+ devices = self._get_connected_devices()
+ self._mac2name = devices
+ return list(devices)
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
- if device in self.mac2name:
- return self.mac2name.get(device)
+ return self._mac2name.get(device)
- return None
-
- def _update_info(self):
- """Retrieve latest information from the Ubee router."""
- if not self.success_init:
- return
-
- _LOGGER.debug("Scanning")
- results = self.get_connected_devices()
-
- if results is None:
- _LOGGER.warning("Error scanning devices")
- return
-
- self.last_results = results or []
-
- def get_connected_devices(self):
+ def _get_connected_devices(self):
"""List connected devices with pyubee."""
- if not self.ubee.session_active():
- self.ubee.login()
+ if not self._ubee.session_active():
+ self._ubee.login()
- return self.ubee.get_connected_devices()
+ return self._ubee.get_connected_devices()
diff --git a/homeassistant/components/ubee/manifest.json b/homeassistant/components/ubee/manifest.json
new file mode 100644
index 00000000000..524dcb1d77b
--- /dev/null
+++ b/homeassistant/components/ubee/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ubee",
+ "name": "Ubee",
+ "documentation": "https://www.home-assistant.io/components/ubee",
+ "requirements": [
+ "pyubee==0.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/uber/manifest.json b/homeassistant/components/uber/manifest.json
new file mode 100644
index 00000000000..a7db237ab91
--- /dev/null
+++ b/homeassistant/components/uber/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "uber",
+ "name": "Uber",
+ "documentation": "https://www.home-assistant.io/components/uber",
+ "requirements": [
+ "uber_rides==0.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/uber/sensor.py b/homeassistant/components/uber/sensor.py
index a97ccaffed0..324124ca960 100644
--- a/homeassistant/components/uber/sensor.py
+++ b/homeassistant/components/uber/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Uber API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.uber/
-"""
+"""Support for the Uber API."""
import logging
from datetime import timedelta
@@ -14,8 +9,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['uber_rides==0.6.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_END_LATITUDE = 'end_latitude'
diff --git a/homeassistant/components/ubus/device_tracker.py b/homeassistant/components/ubus/device_tracker.py
index 96f2f60c1e5..54572524fb2 100644
--- a/homeassistant/components/ubus/device_tracker.py
+++ b/homeassistant/components/ubus/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for OpenWRT (ubus) routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.ubus/
-"""
+"""Support for OpenWRT (ubus) routers."""
import json
import logging
import re
diff --git a/homeassistant/components/ubus/manifest.json b/homeassistant/components/ubus/manifest.json
new file mode 100644
index 00000000000..f886e84254b
--- /dev/null
+++ b/homeassistant/components/ubus/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "ubus",
+ "name": "Ubus",
+ "documentation": "https://www.home-assistant.io/components/ubus",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ue_smart_radio/manifest.json b/homeassistant/components/ue_smart_radio/manifest.json
new file mode 100644
index 00000000000..189ac690758
--- /dev/null
+++ b/homeassistant/components/ue_smart_radio/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "ue_smart_radio",
+ "name": "Ue smart radio",
+ "documentation": "https://www.home-assistant.io/components/ue_smart_radio",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ue_smart_radio/media_player.py b/homeassistant/components/ue_smart_radio/media_player.py
index 2261aadc2f6..0d1f17e10ec 100644
--- a/homeassistant/components/ue_smart_radio/media_player.py
+++ b/homeassistant/components/ue_smart_radio/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Logitech UE Smart Radios.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.ue_smart_radio/
-"""
+"""Support for Logitech UE Smart Radios."""
import logging
diff --git a/homeassistant/components/uk_transport/manifest.json b/homeassistant/components/uk_transport/manifest.json
new file mode 100644
index 00000000000..be44a9d8cc8
--- /dev/null
+++ b/homeassistant/components/uk_transport/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "uk_transport",
+ "name": "Uk transport",
+ "documentation": "https://www.home-assistant.io/components/uk_transport",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/unifi/.translations/fr.json b/homeassistant/components/unifi/.translations/fr.json
index 767962e37eb..9e567fcc394 100644
--- a/homeassistant/components/unifi/.translations/fr.json
+++ b/homeassistant/components/unifi/.translations/fr.json
@@ -1,6 +1,7 @@
{
"config": {
"abort": {
+ "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9",
"user_privilege": "L'utilisateur doit \u00eatre administrateur"
},
"error": {
diff --git a/homeassistant/components/unifi/.translations/th.json b/homeassistant/components/unifi/.translations/th.json
index 178d052c722..3c828bb1182 100644
--- a/homeassistant/components/unifi/.translations/th.json
+++ b/homeassistant/components/unifi/.translations/th.json
@@ -5,7 +5,8 @@
"data": {
"host": "\u0e42\u0e2e\u0e2a\u0e15\u0e4c",
"password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19"
- }
+ },
+ "title": "\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 UniFi Controller"
}
}
}
diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py
index 7e236789a5c..3af450acdbf 100644
--- a/homeassistant/components/unifi/__init__.py
+++ b/homeassistant/components/unifi/__init__.py
@@ -16,8 +16,6 @@ DEFAULT_PORT = 8443
DEFAULT_SITE_ID = 'default'
DEFAULT_VERIFY_SSL = False
-REQUIREMENTS = ['aiounifi==4']
-
async def async_setup(hass, config):
"""Component doesn't support configuration through configuration.yaml."""
diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py
index 2dc5f7a4df3..8bf384eef14 100644
--- a/homeassistant/components/unifi/device_tracker.py
+++ b/homeassistant/components/unifi/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Unifi WAP controllers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.unifi/
-"""
+"""Support for Unifi WAP controllers."""
import logging
from datetime import timedelta
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pyunifi==2.16']
-
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json
new file mode 100644
index 00000000000..85a84539663
--- /dev/null
+++ b/homeassistant/components/unifi/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "unifi",
+ "name": "Unifi",
+ "documentation": "https://www.home-assistant.io/components/unifi",
+ "requirements": [
+ "aiounifi==4",
+ "pyunifi==2.16"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@kane610"
+ ]
+}
diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py
index e90da2dbcd8..5f33a9c08d3 100644
--- a/homeassistant/components/unifi/switch.py
+++ b/homeassistant/components/unifi/switch.py
@@ -11,9 +11,8 @@ from homeassistant.const import CONF_HOST
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
-from .const import CONF_CONTROLLER, CONF_SITE_ID, CONTROLLER_ID, DOMAIN
+from .const import CONF_CONTROLLER, CONF_SITE_ID, CONTROLLER_ID
-DEPENDENCIES = [DOMAIN]
SCAN_INTERVAL = timedelta(seconds=15)
LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/unifi_direct/device_tracker.py b/homeassistant/components/unifi_direct/device_tracker.py
index bd90099e45c..544314c62c5 100644
--- a/homeassistant/components/unifi_direct/device_tracker.py
+++ b/homeassistant/components/unifi_direct/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Unifi AP direct access.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.unifi_direct/
-"""
+"""Support for Unifi AP direct access."""
import logging
import json
@@ -16,8 +11,6 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
CONF_PORT)
-REQUIREMENTS = ['pexpect==4.6.0']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSH_PORT = 22
diff --git a/homeassistant/components/unifi_direct/manifest.json b/homeassistant/components/unifi_direct/manifest.json
new file mode 100644
index 00000000000..515bd68d011
--- /dev/null
+++ b/homeassistant/components/unifi_direct/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "unifi_direct",
+ "name": "Unifi direct",
+ "documentation": "https://www.home-assistant.io/components/unifi_direct",
+ "requirements": [
+ "pexpect==4.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/universal/manifest.json b/homeassistant/components/universal/manifest.json
new file mode 100644
index 00000000000..ac72d10f07f
--- /dev/null
+++ b/homeassistant/components/universal/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "universal",
+ "name": "Universal",
+ "documentation": "https://www.home-assistant.io/components/universal",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py
index 5730a086731..69af20917c5 100644
--- a/homeassistant/components/universal/media_player.py
+++ b/homeassistant/components/universal/media_player.py
@@ -1,9 +1,4 @@
-"""
-Combination of multiple media players into one for a universal controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.universal/
-"""
+"""Combination of multiple media players for a universal controller."""
from copy import copy
import logging
diff --git a/homeassistant/components/universal/services.yaml b/homeassistant/components/universal/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/upc_connect/device_tracker.py b/homeassistant/components/upc_connect/device_tracker.py
index 2ee6d64730d..4b4c32182bd 100644
--- a/homeassistant/components/upc_connect/device_tracker.py
+++ b/homeassistant/components/upc_connect/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for UPC ConnectBox router.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.upc_connect/
-"""
+"""Support for UPC ConnectBox router."""
import asyncio
import logging
@@ -18,8 +13,6 @@ from homeassistant.const import CONF_HOST, HTTP_HEADER_X_REQUESTED_WITH
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['defusedxml==0.5.0']
-
_LOGGER = logging.getLogger(__name__)
CMD_DEVICES = 123
diff --git a/homeassistant/components/upc_connect/manifest.json b/homeassistant/components/upc_connect/manifest.json
new file mode 100644
index 00000000000..926fb9acf88
--- /dev/null
+++ b/homeassistant/components/upc_connect/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "upc_connect",
+ "name": "Upc connect",
+ "documentation": "https://www.home-assistant.io/components/upc_connect",
+ "requirements": [
+ "defusedxml==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py
index 7981cf948bb..ea964c9027d 100644
--- a/homeassistant/components/upcloud/__init__.py
+++ b/homeassistant/components/upcloud/__init__.py
@@ -14,8 +14,6 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['upcloud-api==0.4.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_CORE_NUMBER = 'core_number'
diff --git a/homeassistant/components/upcloud/binary_sensor.py b/homeassistant/components/upcloud/binary_sensor.py
index a0c3c9f34c6..e959f54f254 100644
--- a/homeassistant/components/upcloud/binary_sensor.py
+++ b/homeassistant/components/upcloud/binary_sensor.py
@@ -11,8 +11,6 @@ from . import CONF_SERVERS, DATA_UPCLOUD, UpCloudServerEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['upcloud']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string]),
})
diff --git a/homeassistant/components/upcloud/manifest.json b/homeassistant/components/upcloud/manifest.json
new file mode 100644
index 00000000000..3a58d80f64a
--- /dev/null
+++ b/homeassistant/components/upcloud/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "upcloud",
+ "name": "Upcloud",
+ "documentation": "https://www.home-assistant.io/components/upcloud",
+ "requirements": [
+ "upcloud-api==0.4.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@scop"
+ ]
+}
diff --git a/homeassistant/components/upcloud/switch.py b/homeassistant/components/upcloud/switch.py
index 7e84adccf55..ee1c1498f98 100644
--- a/homeassistant/components/upcloud/switch.py
+++ b/homeassistant/components/upcloud/switch.py
@@ -11,8 +11,6 @@ from . import CONF_SERVERS, DATA_UPCLOUD, UpCloudServerEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['upcloud']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string]),
})
diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py
index cb2646ea942..95b1372418c 100644
--- a/homeassistant/components/updater/__init__.py
+++ b/homeassistant/components/updater/__init__.py
@@ -18,8 +18,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['distro==1.4.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_RELEASE_NOTES = 'release_notes'
diff --git a/homeassistant/components/updater/manifest.json b/homeassistant/components/updater/manifest.json
new file mode 100644
index 00000000000..9275ef34968
--- /dev/null
+++ b/homeassistant/components/updater/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "updater",
+ "name": "Updater",
+ "documentation": "https://www.home-assistant.io/components/updater",
+ "requirements": [
+ "distro==1.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/upnp/.translations/fr.json b/homeassistant/components/upnp/.translations/fr.json
index d1ff04d4824..a87ea9ec9c7 100644
--- a/homeassistant/components/upnp/.translations/fr.json
+++ b/homeassistant/components/upnp/.translations/fr.json
@@ -2,6 +2,7 @@
"config": {
"abort": {
"already_configured": "UPnP / IGD est d\u00e9j\u00e0 configur\u00e9",
+ "incomplete_device": "Ignorer un p\u00e9riph\u00e9rique UPnP incomplet",
"no_devices_discovered": "Aucun UPnP / IGD d\u00e9couvert",
"no_devices_found": "Aucun p\u00e9riph\u00e9rique UPnP / IGD trouv\u00e9 sur le r\u00e9seau.",
"no_sensors_or_port_mapping": "Activer au moins les capteurs ou la cartographie des ports",
diff --git a/homeassistant/components/upnp/.translations/nn.json b/homeassistant/components/upnp/.translations/nn.json
new file mode 100644
index 00000000000..286efcf0353
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/nn.json
@@ -0,0 +1,16 @@
+{
+ "config": {
+ "abort": {
+ "no_sensors_or_port_mapping": "I det minste, aktiver sensor eller portkartlegging"
+ },
+ "error": {
+ "one": "Ein",
+ "other": "Andre"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP / IGD"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/ru.json b/homeassistant/components/upnp/.translations/ru.json
index 668b9a377fc..8d41ec1d5de 100644
--- a/homeassistant/components/upnp/.translations/ru.json
+++ b/homeassistant/components/upnp/.translations/ru.json
@@ -5,7 +5,7 @@
"incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP",
"no_devices_discovered": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e UPnP / IGD",
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.",
- "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432 ",
+ "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432",
"single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
},
"step": {
diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py
index 5f4abcb24c7..01f6d6159f0 100644
--- a/homeassistant/components/upnp/__init__.py
+++ b/homeassistant/components/upnp/__init__.py
@@ -23,8 +23,6 @@ from .const import DOMAIN
from .const import LOGGER as _LOGGER
from .device import Device
-REQUIREMENTS = ['async-upnp-client==0.14.7']
-
NOTIFICATION_ID = 'upnp_notification'
NOTIFICATION_TITLE = 'UPnP/IGD Setup'
diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json
new file mode 100644
index 00000000000..75213ecc9b9
--- /dev/null
+++ b/homeassistant/components/upnp/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "upnp",
+ "name": "Upnp",
+ "documentation": "https://www.home-assistant.io/components/upnp",
+ "requirements": [
+ "async-upnp-client==0.14.7"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py
index 708ef314ab4..411d529b33f 100644
--- a/homeassistant/components/upnp/sensor.py
+++ b/homeassistant/components/upnp/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for UPnP/IGD Sensors.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.upnp/
-"""
+"""Support for UPnP/IGD Sensors."""
from datetime import datetime
import logging
@@ -17,8 +12,6 @@ from .const import DOMAIN as DOMAIN_UPNP, SIGNAL_REMOVE_SENSOR
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['upnp']
-
BYTES_RECEIVED = 'bytes_received'
BYTES_SENT = 'bytes_sent'
PACKETS_RECEIVED = 'packets_received'
diff --git a/homeassistant/components/ups/manifest.json b/homeassistant/components/ups/manifest.json
new file mode 100644
index 00000000000..98db00c3094
--- /dev/null
+++ b/homeassistant/components/ups/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ups",
+ "name": "Ups",
+ "documentation": "https://www.home-assistant.io/components/ups",
+ "requirements": [
+ "upsmychoice==1.0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ups/sensor.py b/homeassistant/components/ups/sensor.py
index e4aab555050..55451d4bbfd 100644
--- a/homeassistant/components/ups/sensor.py
+++ b/homeassistant/components/ups/sensor.py
@@ -1,27 +1,19 @@
-"""
-Sensor for UPS packages.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.ups/
-"""
-from collections import defaultdict
+"""Sensor for UPS packages."""
import logging
+from collections import defaultdict
from datetime import timedelta
import voluptuous as vol
-from homeassistant.components.sensor import PLATFORM_SCHEMA
-from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
- ATTR_ATTRIBUTION, CONF_UPDATE_INTERVAL,
- CONF_SCAN_INTERVAL,
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
-from homeassistant.helpers.entity import Entity
-from homeassistant.util import slugify
-from homeassistant.util import Throttle
-from homeassistant.util.dt import now, parse_date
import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['upsmychoice==1.0.6']
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_NAME, CONF_PASSWORD, CONF_SCAN_INTERVAL,
+ CONF_USERNAME
+)
+from homeassistant.helpers.entity import Entity
+from homeassistant.util import Throttle, slugify
+from homeassistant.util.dt import now, parse_date
_LOGGER = logging.getLogger(__name__)
@@ -32,21 +24,11 @@ STATUS_DELIVERED = 'delivered'
SCAN_INTERVAL = timedelta(seconds=1800)
-PLATFORM_SCHEMA = vol.All(
- PLATFORM_SCHEMA.extend({
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_NAME): cv.string,
- vol.Optional(CONF_UPDATE_INTERVAL): (
- vol.All(cv.time_period, cv.positive_timedelta)),
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=SCAN_INTERVAL
- )
-)
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_NAME): cv.string,
+})
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/uptime/manifest.json b/homeassistant/components/uptime/manifest.json
new file mode 100644
index 00000000000..10197178381
--- /dev/null
+++ b/homeassistant/components/uptime/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "uptime",
+ "name": "Uptime",
+ "documentation": "https://www.home-assistant.io/components/uptime",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/uptime/sensor.py b/homeassistant/components/uptime/sensor.py
index 197233461fb..7e741499f73 100644
--- a/homeassistant/components/uptime/sensor.py
+++ b/homeassistant/components/uptime/sensor.py
@@ -1,9 +1,4 @@
-"""
-Platform to retrieve uptime for Home Assistant.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.uptime/
-"""
+"""Platform to retrieve uptime for Home Assistant."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py
index e48ac3039ae..90b71c026dc 100644
--- a/homeassistant/components/uptimerobot/binary_sensor.py
+++ b/homeassistant/components/uptimerobot/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-A platform that to monitor Uptime Robot monitors.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/binary_sensor.uptimerobot/
-"""
+"""A platform that to monitor Uptime Robot monitors."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyuptimerobot==0.0.5']
-
_LOGGER = logging.getLogger(__name__)
ATTR_TARGET = 'target'
diff --git a/homeassistant/components/uptimerobot/manifest.json b/homeassistant/components/uptimerobot/manifest.json
new file mode 100644
index 00000000000..375baf12565
--- /dev/null
+++ b/homeassistant/components/uptimerobot/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "uptimerobot",
+ "name": "Uptimerobot",
+ "documentation": "https://www.home-assistant.io/components/uptimerobot",
+ "requirements": [
+ "pyuptimerobot==0.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@ludeeus"
+ ]
+}
diff --git a/homeassistant/components/uscis/manifest.json b/homeassistant/components/uscis/manifest.json
new file mode 100644
index 00000000000..f2ffcfbf8a3
--- /dev/null
+++ b/homeassistant/components/uscis/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "uscis",
+ "name": "Uscis",
+ "documentation": "https://www.home-assistant.io/components/uscis",
+ "requirements": [
+ "uscisstatus==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py
index e3a917b0a5a..59b37c7ea65 100644
--- a/homeassistant/components/uscis/sensor.py
+++ b/homeassistant/components/uscis/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for USCIS Case Status.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.uscis/
-"""
+"""Support for USCIS Case Status."""
import logging
from datetime import timedelta
@@ -18,8 +13,6 @@ from homeassistant.const import CONF_FRIENDLY_NAME
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['uscisstatus==0.1.1']
-
DEFAULT_NAME = "USCIS"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py
index 1d11b1971cc..60d1f6925a4 100644
--- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py
+++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['geojson_client==0.3']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ALERT = 'alert'
diff --git a/homeassistant/components/usgs_earthquakes_feed/manifest.json b/homeassistant/components/usgs_earthquakes_feed/manifest.json
new file mode 100644
index 00000000000..0b3848dbde6
--- /dev/null
+++ b/homeassistant/components/usgs_earthquakes_feed/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "usgs_earthquakes_feed",
+ "name": "Usgs earthquakes feed",
+ "documentation": "https://www.home-assistant.io/components/usgs_earthquakes_feed",
+ "requirements": [
+ "geojson_client==0.3"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/usps/__init__.py b/homeassistant/components/usps/__init__.py
index 8a7d7d52255..eb2882d2a56 100644
--- a/homeassistant/components/usps/__init__.py
+++ b/homeassistant/components/usps/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.helpers import (config_validation as cv, discovery)
from homeassistant.util import Throttle
from homeassistant.util.dt import now
-REQUIREMENTS = ['myusps==1.3.2']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'usps'
diff --git a/homeassistant/components/usps/camera.py b/homeassistant/components/usps/camera.py
index 5b5eaca4ce2..cd0a216517b 100644
--- a/homeassistant/components/usps/camera.py
+++ b/homeassistant/components/usps/camera.py
@@ -8,8 +8,6 @@ from . import DATA_USPS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['usps']
-
SCAN_INTERVAL = timedelta(seconds=10)
diff --git a/homeassistant/components/usps/manifest.json b/homeassistant/components/usps/manifest.json
new file mode 100644
index 00000000000..9e2f8886d3a
--- /dev/null
+++ b/homeassistant/components/usps/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "usps",
+ "name": "Usps",
+ "documentation": "https://www.home-assistant.io/components/usps",
+ "requirements": [
+ "myusps==1.3.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/usps/sensor.py b/homeassistant/components/usps/sensor.py
index 3e5fea5c4ee..4580978da75 100644
--- a/homeassistant/components/usps/sensor.py
+++ b/homeassistant/components/usps/sensor.py
@@ -11,8 +11,6 @@ from . import DATA_USPS
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['usps']
-
STATUS_DELIVERED = 'delivered'
diff --git a/homeassistant/components/utility_meter/manifest.json b/homeassistant/components/utility_meter/manifest.json
new file mode 100644
index 00000000000..59f4d1ca21b
--- /dev/null
+++ b/homeassistant/components/utility_meter/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "utility_meter",
+ "name": "Utility meter",
+ "documentation": "https://www.home-assistant.io/components/utility_meter",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@dgomes"
+ ]
+}
diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py
index 50e7c3d8fe2..423c27e0781 100644
--- a/homeassistant/components/uvc/camera.py
+++ b/homeassistant/components/uvc/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for Ubiquiti's UVC cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.uvc/
-"""
+"""Support for Ubiquiti's UVC cameras."""
import logging
import socket
@@ -15,8 +10,6 @@ from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
-REQUIREMENTS = ['uvcclient==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
CONF_NVR = 'nvr'
diff --git a/homeassistant/components/uvc/manifest.json b/homeassistant/components/uvc/manifest.json
new file mode 100644
index 00000000000..5c77f9ecc70
--- /dev/null
+++ b/homeassistant/components/uvc/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "uvc",
+ "name": "Uvc",
+ "documentation": "https://www.home-assistant.io/components/uvc",
+ "requirements": [
+ "uvcclient==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py
index 3fdc7cb1a51..0e44d494b56 100644
--- a/homeassistant/components/vacuum/__init__.py
+++ b/homeassistant/components/vacuum/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for vacuum cleaner robots (botvacs).
-
-For more details about this platform, please refer to the documentation
-https://home-assistant.io/components/vacuum/
-"""
+"""Support for vacuum cleaner robots (botvacs)."""
from datetime import timedelta
from functools import partial
import logging
@@ -25,8 +20,6 @@ from homeassistant.helpers.icon import icon_for_battery_level
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'vacuum'
-DEPENDENCIES = ['group']
-
SCAN_INTERVAL = timedelta(seconds=20)
GROUP_NAME_ALL_VACUUMS = 'all vacuum cleaners'
diff --git a/homeassistant/components/vacuum/manifest.json b/homeassistant/components/vacuum/manifest.json
new file mode 100644
index 00000000000..8dfbb8ed968
--- /dev/null
+++ b/homeassistant/components/vacuum/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vacuum",
+ "name": "Vacuum",
+ "documentation": "https://www.home-assistant.io/components/vacuum",
+ "requirements": [],
+ "dependencies": [
+ "group"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vasttrafik/manifest.json b/homeassistant/components/vasttrafik/manifest.json
new file mode 100644
index 00000000000..47153dcf17f
--- /dev/null
+++ b/homeassistant/components/vasttrafik/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vasttrafik",
+ "name": "Vasttrafik",
+ "documentation": "https://www.home-assistant.io/components/vasttrafik",
+ "requirements": [
+ "vtjp==0.1.14"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py
index 8148a5c2fc7..45279fa8933 100644
--- a/homeassistant/components/vasttrafik/sensor.py
+++ b/homeassistant/components/vasttrafik/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Västtrafik public transport.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.vasttrafik/
-"""
+"""Support for Västtrafik public transport."""
from datetime import datetime
from datetime import timedelta
import logging
@@ -16,8 +11,6 @@ from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['vtjp==0.1.14']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ACCESSIBILITY = 'accessibility'
diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py
index 4e808dc21ca..73cd0d734bd 100644
--- a/homeassistant/components/velbus/__init__.py
+++ b/homeassistant/components/velbus/__init__.py
@@ -7,8 +7,6 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_PORT
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-velbus==2.0.22']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'velbus'
diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py
index cbe1350bd4f..82a1c5568fc 100644
--- a/homeassistant/components/velbus/binary_sensor.py
+++ b/homeassistant/components/velbus/binary_sensor.py
@@ -7,8 +7,6 @@ from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['velbus']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py
index 470524bb6f3..0471e5b87e0 100644
--- a/homeassistant/components/velbus/climate.py
+++ b/homeassistant/components/velbus/climate.py
@@ -10,8 +10,6 @@ from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['velbus']
-
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py
index b176ab76c4b..fb9cea93455 100644
--- a/homeassistant/components/velbus/cover.py
+++ b/homeassistant/components/velbus/cover.py
@@ -24,8 +24,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
})
-DEPENDENCIES = ['velbus']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up cover controlled by Velbus."""
diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json
new file mode 100644
index 00000000000..ff32000d5f0
--- /dev/null
+++ b/homeassistant/components/velbus/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "velbus",
+ "name": "Velbus",
+ "documentation": "https://www.home-assistant.io/components/velbus",
+ "requirements": [
+ "python-velbus==2.0.24"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py
index ad78a795a30..b8287aef41a 100644
--- a/homeassistant/components/velbus/sensor.py
+++ b/homeassistant/components/velbus/sensor.py
@@ -5,8 +5,6 @@ from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['velbus']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py
index b5ef89ca480..0835e2bd209 100644
--- a/homeassistant/components/velbus/switch.py
+++ b/homeassistant/components/velbus/switch.py
@@ -7,8 +7,6 @@ from . import DOMAIN as VELBUS_DOMAIN, VelbusEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['velbus']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py
index a46f62dbd5f..1a1444f22ae 100644
--- a/homeassistant/components/velux/__init__.py
+++ b/homeassistant/components/velux/__init__.py
@@ -12,8 +12,6 @@ DATA_VELUX = "data_velux"
SUPPORTED_DOMAINS = ['cover', 'scene']
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyvlx==0.2.10']
-
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py
index 1893909b706..3c1b6ecb1eb 100644
--- a/homeassistant/components/velux/cover.py
+++ b/homeassistant/components/velux/cover.py
@@ -6,8 +6,6 @@ from homeassistant.core import callback
from . import DATA_VELUX
-DEPENDENCIES = ['velux']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json
new file mode 100644
index 00000000000..bac2587cdc9
--- /dev/null
+++ b/homeassistant/components/velux/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "velux",
+ "name": "Velux",
+ "documentation": "https://www.home-assistant.io/components/velux",
+ "requirements": [
+ "pyvlx==0.2.10"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@Julius2342"
+ ]
+}
diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py
index 614d3f349a2..f33296780d7 100644
--- a/homeassistant/components/velux/scene.py
+++ b/homeassistant/components/velux/scene.py
@@ -3,8 +3,6 @@ from homeassistant.components.scene import Scene
from . import _LOGGER, DATA_VELUX
-DEPENDENCIES = ['velux']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py
index 820443ee186..68b6ff88857 100644
--- a/homeassistant/components/venstar/climate.py
+++ b/homeassistant/components/venstar/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for Venstar WiFi Thermostats.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.venstar/
-"""
+"""Support for Venstar WiFi Thermostats."""
import logging
import voluptuous as vol
@@ -22,8 +17,6 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['venstarcolortouch==0.6']
-
_LOGGER = logging.getLogger(__name__)
ATTR_FAN_STATE = 'fan_state'
diff --git a/homeassistant/components/venstar/manifest.json b/homeassistant/components/venstar/manifest.json
new file mode 100644
index 00000000000..e8b9158f721
--- /dev/null
+++ b/homeassistant/components/venstar/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "venstar",
+ "name": "Venstar",
+ "documentation": "https://www.home-assistant.io/components/venstar",
+ "requirements": [
+ "venstarcolortouch==0.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py
index 3f4c66d238a..1c5d9f811ad 100644
--- a/homeassistant/components/vera/__init__.py
+++ b/homeassistant/components/vera/__init__.py
@@ -14,8 +14,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_LIGHTS, CONF_EXCLUDE)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['pyvera==0.2.45']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'vera'
diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py
index c81fa31938f..7482e39e721 100644
--- a/homeassistant/components/vera/binary_sensor.py
+++ b/homeassistant/components/vera/binary_sensor.py
@@ -6,8 +6,6 @@ from homeassistant.components.binary_sensor import (
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
-DEPENDENCIES = ['vera']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py
index f8ff9c21b89..dba074f73ef 100644
--- a/homeassistant/components/vera/climate.py
+++ b/homeassistant/components/vera/climate.py
@@ -11,8 +11,6 @@ from homeassistant.util import convert
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
-DEPENDENCIES = ['vera']
-
_LOGGER = logging.getLogger(__name__)
OPERATION_LIST = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_OFF]
diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py
index 4cf2aac3bb4..ac61a913128 100644
--- a/homeassistant/components/vera/cover.py
+++ b/homeassistant/components/vera/cover.py
@@ -6,8 +6,6 @@ from homeassistant.components.cover import (
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
-DEPENDENCIES = ['vera']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py
index e4e315bb52e..4ea9ad4400a 100644
--- a/homeassistant/components/vera/light.py
+++ b/homeassistant/components/vera/light.py
@@ -10,8 +10,6 @@ from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['vera']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera lights."""
diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py
index 5ace07b87d7..9ceb06d8a86 100644
--- a/homeassistant/components/vera/lock.py
+++ b/homeassistant/components/vera/lock.py
@@ -8,8 +8,6 @@ from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['vera']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Find and return Vera locks."""
diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json
new file mode 100644
index 00000000000..7b475c437c3
--- /dev/null
+++ b/homeassistant/components/vera/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vera",
+ "name": "Vera",
+ "documentation": "https://www.home-assistant.io/components/vera",
+ "requirements": [
+ "pyvera==0.2.45"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py
index 5000f9bc50f..f3659fa3e9b 100644
--- a/homeassistant/components/vera/scene.py
+++ b/homeassistant/components/vera/scene.py
@@ -8,8 +8,6 @@ from . import VERA_CONTROLLER, VERA_ID_FORMAT, VERA_SCENES
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['vera']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera scenes."""
diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py
index 3c026046b3e..caec102eb1f 100644
--- a/homeassistant/components/vera/sensor.py
+++ b/homeassistant/components/vera/sensor.py
@@ -11,8 +11,6 @@ from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['vera']
-
SCAN_INTERVAL = timedelta(seconds=5)
diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py
index f422e49bf42..0f7654c9720 100644
--- a/homeassistant/components/vera/switch.py
+++ b/homeassistant/components/vera/switch.py
@@ -8,8 +8,6 @@ from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['vera']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera switches."""
diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py
index 393a4066002..195f065ee85 100644
--- a/homeassistant/components/verisure/__init__.py
+++ b/homeassistant/components/verisure/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import discovery
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['vsure==1.5.2', 'jsonpath==0.75']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DEVICE_SERIAL = 'device_serial'
diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json
new file mode 100644
index 00000000000..7c895233f77
--- /dev/null
+++ b/homeassistant/components/verisure/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "verisure",
+ "name": "Verisure",
+ "documentation": "https://www.home-assistant.io/components/verisure",
+ "requirements": [
+ "jsonpath==0.75",
+ "vsure==1.5.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/version/manifest.json b/homeassistant/components/version/manifest.json
new file mode 100644
index 00000000000..16d11e913f7
--- /dev/null
+++ b/homeassistant/components/version/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "version",
+ "name": "Version",
+ "documentation": "https://www.home-assistant.io/components/version",
+ "requirements": [
+ "pyhaversion==2.2.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py
index 8a2a7593b2c..6aed6da17f7 100644
--- a/homeassistant/components/version/sensor.py
+++ b/homeassistant/components/version/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor that can display the current Home Assistant versions.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.version/
-"""
+"""Sensor that can display the current Home Assistant versions."""
import logging
from datetime import timedelta
@@ -16,8 +11,6 @@ from homeassistant.const import CONF_NAME, CONF_SOURCE
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyhaversion==2.0.3']
-
_LOGGER = logging.getLogger(__name__)
ALL_IMAGES = [
diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json
new file mode 100644
index 00000000000..bba754d135f
--- /dev/null
+++ b/homeassistant/components/vesync/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vesync",
+ "name": "Vesync",
+ "documentation": "https://www.home-assistant.io/components/vesync",
+ "requirements": [
+ "pyvesync_v2==0.9.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py
index d9ffbf9c12d..d8fa3d317ff 100644
--- a/homeassistant/components/vesync/switch.py
+++ b/homeassistant/components/vesync/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for Etekcity VeSync switches.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.vesync/
-"""
+"""Support for Etekcity VeSync switches."""
import logging
import voluptuous as vol
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
@@ -11,8 +6,6 @@ from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyvesync_v2==0.9.6']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/viaggiatreno/manifest.json b/homeassistant/components/viaggiatreno/manifest.json
new file mode 100644
index 00000000000..e145b26b0c9
--- /dev/null
+++ b/homeassistant/components/viaggiatreno/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "viaggiatreno",
+ "name": "Viaggiatreno",
+ "documentation": "https://www.home-assistant.io/components/viaggiatreno",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/viaggiatreno/sensor.py b/homeassistant/components/viaggiatreno/sensor.py
index 2b8de2042fa..ee939d4a594 100644
--- a/homeassistant/components/viaggiatreno/sensor.py
+++ b/homeassistant/components/viaggiatreno/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for information about the Italian train system using ViaggiaTreno API.
-
-For more details about this platform please refer to the documentation at
-https://home-assistant.io/components/sensor.viaggiatreno
-"""
+"""Support for the Italian train system using ViaggiaTreno API."""
import asyncio
import logging
diff --git a/homeassistant/components/vizio/manifest.json b/homeassistant/components/vizio/manifest.json
new file mode 100644
index 00000000000..ac589de841a
--- /dev/null
+++ b/homeassistant/components/vizio/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vizio",
+ "name": "Vizio",
+ "documentation": "https://www.home-assistant.io/components/vizio",
+ "requirements": [
+ "pyvizio==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py
index af3fdd1e15a..7b47a388325 100644
--- a/homeassistant/components/vizio/media_player.py
+++ b/homeassistant/components/vizio/media_player.py
@@ -1,9 +1,4 @@
-"""
-Vizio SmartCast TV support.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.vizio/
-"""
+"""Vizio SmartCast TV support."""
from datetime import timedelta
import logging
@@ -20,8 +15,6 @@ from homeassistant.const import (
CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON)
from homeassistant.helpers import config_validation as cv
-REQUIREMENTS = ['pyvizio==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_SUPPRESS_WARNING = 'suppress_warning'
diff --git a/homeassistant/components/vlc/manifest.json b/homeassistant/components/vlc/manifest.json
new file mode 100644
index 00000000000..a40b0e8c7d6
--- /dev/null
+++ b/homeassistant/components/vlc/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vlc",
+ "name": "Vlc",
+ "documentation": "https://www.home-assistant.io/components/vlc",
+ "requirements": [
+ "python-vlc==1.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py
index 592243938d7..be930d02b0c 100644
--- a/homeassistant/components/vlc/media_player.py
+++ b/homeassistant/components/vlc/media_player.py
@@ -1,9 +1,4 @@
-"""
-Provide functionality to interact with vlc devices on the network.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.vlc/
-"""
+"""Provide functionality to interact with vlc devices on the network."""
import logging
import voluptuous as vol
@@ -18,8 +13,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['python-vlc==1.1.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_ARGUMENTS = 'arguments'
diff --git a/homeassistant/components/voicerss/manifest.json b/homeassistant/components/voicerss/manifest.json
new file mode 100644
index 00000000000..6f0b4ae5fd2
--- /dev/null
+++ b/homeassistant/components/voicerss/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "voicerss",
+ "name": "Voicerss",
+ "documentation": "https://www.home-assistant.io/components/voicerss",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/voicerss/tts.py b/homeassistant/components/voicerss/tts.py
index 436f070e503..d5340e45b5c 100644
--- a/homeassistant/components/voicerss/tts.py
+++ b/homeassistant/components/voicerss/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the voicerss speech service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts.voicerss/
-"""
+"""Support for the voicerss speech service."""
import asyncio
import logging
diff --git a/homeassistant/components/volkszaehler/manifest.json b/homeassistant/components/volkszaehler/manifest.json
new file mode 100644
index 00000000000..db068e35056
--- /dev/null
+++ b/homeassistant/components/volkszaehler/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "volkszaehler",
+ "name": "Volkszaehler",
+ "documentation": "https://www.home-assistant.io/components/volkszaehler",
+ "requirements": [
+ "volkszaehler==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/volkszaehler/sensor.py b/homeassistant/components/volkszaehler/sensor.py
index e67d9d6424a..550dc395ee7 100644
--- a/homeassistant/components/volkszaehler/sensor.py
+++ b/homeassistant/components/volkszaehler/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for consuming values for the Volkszaehler API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.volkszaehler/
-"""
+"""Support for consuming values for the Volkszaehler API."""
from datetime import timedelta
import logging
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['volkszaehler==0.1.2']
-
_LOGGER = logging.getLogger(__name__)
CONF_UUID = 'uuid'
diff --git a/homeassistant/components/volumio/manifest.json b/homeassistant/components/volumio/manifest.json
new file mode 100644
index 00000000000..e7c4bac4abd
--- /dev/null
+++ b/homeassistant/components/volumio/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "volumio",
+ "name": "Volumio",
+ "documentation": "https://www.home-assistant.io/components/volumio",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py
index 7e72607c2f3..88ab41994be 100644
--- a/homeassistant/components/volvooncall/__init__.py
+++ b/homeassistant/components/volvooncall/__init__.py
@@ -1,29 +1,26 @@
"""Support for Volvo On Call."""
-from datetime import timedelta
import logging
+from datetime import timedelta
import voluptuous as vol
-from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
- CONF_NAME, CONF_RESOURCES,
- CONF_UPDATE_INTERVAL, CONF_SCAN_INTERVAL,
- CONF_UPDATE_INTERVAL_INVALIDATION_VERSION)
-from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import Entity
-from homeassistant.helpers.event import async_track_point_in_utc_time
+from homeassistant.const import (
+ CONF_NAME, CONF_PASSWORD, CONF_RESOURCES, CONF_SCAN_INTERVAL, CONF_USERNAME
+)
+from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
- async_dispatcher_send,
- async_dispatcher_connect)
+ async_dispatcher_connect, async_dispatcher_send
+)
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
DOMAIN = 'volvooncall'
DATA_KEY = DOMAIN
-REQUIREMENTS = ['volvooncall==0.8.7']
-
_LOGGER = logging.getLogger(__name__)
MIN_UPDATE_INTERVAL = timedelta(minutes=1)
@@ -84,30 +81,20 @@ RESOURCES = [
]
CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.All(
- vol.Schema({
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_UPDATE_INTERVAL):
- vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)),
- vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_UPDATE_INTERVAL):
- vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)),
- vol.Optional(CONF_NAME, default={}):
- cv.schema_with_slug_keys(cv.string),
- vol.Optional(CONF_RESOURCES): vol.All(
- cv.ensure_list, [vol.In(RESOURCES)]),
- vol.Optional(CONF_REGION): cv.string,
- vol.Optional(CONF_SERVICE_URL): cv.string,
- vol.Optional(CONF_MUTABLE, default=True): cv.boolean,
- vol.Optional(CONF_SCANDINAVIAN_MILES, default=False): cv.boolean,
- }),
- cv.deprecated(
- CONF_UPDATE_INTERVAL,
- replacement_key=CONF_SCAN_INTERVAL,
- invalidation_version=CONF_UPDATE_INTERVAL_INVALIDATION_VERSION,
- default=DEFAULT_UPDATE_INTERVAL
- )
- )
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_UPDATE_INTERVAL):
+ vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)),
+ vol.Optional(CONF_NAME, default={}):
+ cv.schema_with_slug_keys(cv.string),
+ vol.Optional(CONF_RESOURCES): vol.All(
+ cv.ensure_list, [vol.In(RESOURCES)]),
+ vol.Optional(CONF_REGION): cv.string,
+ vol.Optional(CONF_SERVICE_URL): cv.string,
+ vol.Optional(CONF_MUTABLE, default=True): cv.boolean,
+ vol.Optional(CONF_SCANDINAVIAN_MILES, default=False): cv.boolean,
+ })
}, extra=vol.ALLOW_EXTRA)
diff --git a/homeassistant/components/volvooncall/manifest.json b/homeassistant/components/volvooncall/manifest.json
new file mode 100644
index 00000000000..aa691d7766c
--- /dev/null
+++ b/homeassistant/components/volvooncall/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "volvooncall",
+ "name": "Volvooncall",
+ "documentation": "https://www.home-assistant.io/components/volvooncall",
+ "requirements": [
+ "volvooncall==0.8.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vultr/__init__.py b/homeassistant/components/vultr/__init__.py
index 9f2efabd412..d7f5b30507a 100644
--- a/homeassistant/components/vultr/__init__.py
+++ b/homeassistant/components/vultr/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['vultr==0.1.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_AUTO_BACKUPS = 'auto_backups'
diff --git a/homeassistant/components/vultr/binary_sensor.py b/homeassistant/components/vultr/binary_sensor.py
index dccb648c9c2..087f38b77f5 100644
--- a/homeassistant/components/vultr/binary_sensor.py
+++ b/homeassistant/components/vultr/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring the state of Vultr subscriptions (VPS).
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.vultr/
-"""
+"""Support for monitoring the state of Vultr subscriptions (VPS)."""
import logging
import voluptuous as vol
@@ -23,8 +18,6 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_DEVICE_CLASS = 'power'
DEFAULT_NAME = 'Vultr {}'
-DEPENDENCIES = ['vultr']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SUBSCRIPTION): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
diff --git a/homeassistant/components/vultr/manifest.json b/homeassistant/components/vultr/manifest.json
new file mode 100644
index 00000000000..5f5461f2d63
--- /dev/null
+++ b/homeassistant/components/vultr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "vultr",
+ "name": "Vultr",
+ "documentation": "https://www.home-assistant.io/components/vultr",
+ "requirements": [
+ "vultr==0.1.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/vultr/sensor.py b/homeassistant/components/vultr/sensor.py
index 7ca731cabac..4f9692fe5c8 100644
--- a/homeassistant/components/vultr/sensor.py
+++ b/homeassistant/components/vultr/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for monitoring the state of Vultr Subscriptions.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/sensor.vultr/
-"""
+"""Support for monitoring the state of Vultr Subscriptions."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Vultr {} {}'
-DEPENDENCIES = ['vultr']
-
MONITORED_CONDITIONS = {
ATTR_CURRENT_BANDWIDTH_USED: ['Current Bandwidth Used', 'GB',
'mdi:chart-histogram'],
diff --git a/homeassistant/components/vultr/switch.py b/homeassistant/components/vultr/switch.py
index 1f7d9ceaa28..33eeafbab68 100644
--- a/homeassistant/components/vultr/switch.py
+++ b/homeassistant/components/vultr/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for interacting with Vultr subscriptions.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/switch.vultr/
-"""
+"""Support for interacting with Vultr subscriptions."""
import logging
import voluptuous as vol
@@ -21,8 +16,6 @@ from . import (
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Vultr {}'
-DEPENDENCIES = ['vultr']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SUBSCRIPTION): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
diff --git a/homeassistant/components/w800rf32/__init__.py b/homeassistant/components/w800rf32/__init__.py
index d2c0cf6b968..920a90fbc52 100644
--- a/homeassistant/components/w800rf32/__init__.py
+++ b/homeassistant/components/w800rf32/__init__.py
@@ -10,8 +10,6 @@ from homeassistant.const import (CONF_DEVICE,
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (dispatcher_send)
-REQUIREMENTS = ['pyW800rf32==0.1']
-
DATA_W800RF32 = 'data_w800rf32'
DOMAIN = 'w800rf32'
diff --git a/homeassistant/components/w800rf32/binary_sensor.py b/homeassistant/components/w800rf32/binary_sensor.py
index c9424834953..caa3771b88e 100644
--- a/homeassistant/components/w800rf32/binary_sensor.py
+++ b/homeassistant/components/w800rf32/binary_sensor.py
@@ -15,7 +15,6 @@ from . import W800RF32_DEVICE
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['w800rf32']
CONF_OFF_DELAY = 'off_delay'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/w800rf32/manifest.json b/homeassistant/components/w800rf32/manifest.json
new file mode 100644
index 00000000000..920ee1120a7
--- /dev/null
+++ b/homeassistant/components/w800rf32/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "w800rf32",
+ "name": "W800rf32",
+ "documentation": "https://www.home-assistant.io/components/w800rf32",
+ "requirements": [
+ "pyW800rf32==0.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/wake_on_lan/__init__.py b/homeassistant/components/wake_on_lan/__init__.py
index e6e12ef0afe..064568cdf1b 100644
--- a/homeassistant/components/wake_on_lan/__init__.py
+++ b/homeassistant/components/wake_on_lan/__init__.py
@@ -7,8 +7,6 @@ import voluptuous as vol
from homeassistant.const import CONF_MAC
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['wakeonlan==1.1.6']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'wake_on_lan'
diff --git a/homeassistant/components/wake_on_lan/manifest.json b/homeassistant/components/wake_on_lan/manifest.json
new file mode 100644
index 00000000000..dc689f8d617
--- /dev/null
+++ b/homeassistant/components/wake_on_lan/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "wake_on_lan",
+ "name": "Wake on lan",
+ "documentation": "https://www.home-assistant.io/components/wake_on_lan",
+ "requirements": [
+ "wakeonlan==1.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/wake_on_lan/switch.py b/homeassistant/components/wake_on_lan/switch.py
index 16bd700e1d5..e08e531a644 100644
--- a/homeassistant/components/wake_on_lan/switch.py
+++ b/homeassistant/components/wake_on_lan/switch.py
@@ -1,9 +1,4 @@
-"""
-Support for wake on lan.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/switch.wake_on_lan/
-"""
+"""Support for wake on lan."""
import logging
import platform
import subprocess as sp
@@ -15,8 +10,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script
-REQUIREMENTS = ['wakeonlan==1.1.6']
-
_LOGGER = logging.getLogger(__name__)
CONF_BROADCAST_ADDRESS = 'broadcast_address'
diff --git a/homeassistant/components/waqi/manifest.json b/homeassistant/components/waqi/manifest.json
new file mode 100644
index 00000000000..4b692c669d1
--- /dev/null
+++ b/homeassistant/components/waqi/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "waqi",
+ "name": "Waqi",
+ "documentation": "https://www.home-assistant.io/components/waqi",
+ "requirements": [
+ "waqiasync==1.0.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@andrey-git"
+ ]
+}
diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py
index d6b8d278fb1..451b8173562 100644
--- a/homeassistant/components/waqi/sensor.py
+++ b/homeassistant/components/waqi/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the World Air Quality Index service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.waqi/
-"""
+"""Support for the World Air Quality Index service."""
import asyncio
import logging
from datetime import timedelta
@@ -19,8 +14,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['waqiasync==1.0.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DOMINENTPOL = 'dominentpol'
diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py
index 6c3cc7405ba..2ecd2522559 100644
--- a/homeassistant/components/water_heater/__init__.py
+++ b/homeassistant/components/water_heater/__init__.py
@@ -45,6 +45,9 @@ ATTR_MIN_TEMP = 'min_temp'
ATTR_AWAY_MODE = 'away_mode'
ATTR_OPERATION_MODE = 'operation_mode'
ATTR_OPERATION_LIST = 'operation_list'
+ATTR_TARGET_TEMP_HIGH = 'target_temp_high'
+ATTR_TARGET_TEMP_LOW = 'target_temp_low'
+ATTR_CURRENT_TEMPERATURE = 'current_temperature'
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
@@ -132,6 +135,9 @@ class WaterHeaterDevice(Entity):
def state_attributes(self):
"""Return the optional state attributes."""
data = {
+ ATTR_CURRENT_TEMPERATURE: show_temp(
+ self.hass, self.current_temperature, self.temperature_unit,
+ self.precision),
ATTR_MIN_TEMP: show_temp(
self.hass, self.min_temp, self.temperature_unit,
self.precision),
@@ -141,6 +147,12 @@ class WaterHeaterDevice(Entity):
ATTR_TEMPERATURE: show_temp(
self.hass, self.target_temperature, self.temperature_unit,
self.precision),
+ ATTR_TARGET_TEMP_HIGH: show_temp(
+ self.hass, self.target_temperature_high, self.temperature_unit,
+ self.precision),
+ ATTR_TARGET_TEMP_LOW: show_temp(
+ self.hass, self.target_temperature_low, self.temperature_unit,
+ self.precision),
}
supported_features = self.supported_features
@@ -171,11 +183,26 @@ class WaterHeaterDevice(Entity):
"""Return the list of available operation modes."""
return None
+ @property
+ def current_temperature(self):
+ """Return the current temperature."""
+ return None
+
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return None
+ @property
+ def target_temperature_high(self):
+ """Return the highbound target temperature we try to reach."""
+ return None
+
+ @property
+ def target_temperature_low(self):
+ """Return the lowbound target temperature we try to reach."""
+ return None
+
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
diff --git a/homeassistant/components/water_heater/manifest.json b/homeassistant/components/water_heater/manifest.json
new file mode 100644
index 00000000000..e291777483e
--- /dev/null
+++ b/homeassistant/components/water_heater/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "water_heater",
+ "name": "Water heater",
+ "documentation": "https://www.home-assistant.io/components/water_heater",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/waterfurnace/__init__.py b/homeassistant/components/waterfurnace/__init__.py
index 38fd44cd1c7..848037f584e 100644
--- a/homeassistant/components/waterfurnace/__init__.py
+++ b/homeassistant/components/waterfurnace/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
-REQUIREMENTS = ['waterfurnace==1.1.0']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'waterfurnace'
diff --git a/homeassistant/components/waterfurnace/manifest.json b/homeassistant/components/waterfurnace/manifest.json
new file mode 100644
index 00000000000..57aa663a348
--- /dev/null
+++ b/homeassistant/components/waterfurnace/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "waterfurnace",
+ "name": "Waterfurnace",
+ "documentation": "https://www.home-assistant.io/components/waterfurnace",
+ "requirements": [
+ "waterfurnace==1.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/waterfurnace/sensor.py b/homeassistant/components/waterfurnace/sensor.py
index 8a43a7dac77..8b1fc46312c 100644
--- a/homeassistant/components/waterfurnace/sensor.py
+++ b/homeassistant/components/waterfurnace/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Waterfurnace.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.waterfurnace/
-"""
+"""Support for Waterfurnace."""
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.const import TEMP_FAHRENHEIT
diff --git a/homeassistant/components/watson_iot/__init__.py b/homeassistant/components/watson_iot/__init__.py
index e9a907ee6d2..cefce56de07 100644
--- a/homeassistant/components/watson_iot/__init__.py
+++ b/homeassistant/components/watson_iot/__init__.py
@@ -13,8 +13,6 @@ from homeassistant.const import (
from homeassistant.helpers import state as state_helper
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['ibmiotf==0.3.4']
-
_LOGGER = logging.getLogger(__name__)
CONF_ORG = 'organization'
diff --git a/homeassistant/components/watson_iot/manifest.json b/homeassistant/components/watson_iot/manifest.json
new file mode 100644
index 00000000000..8896f34f976
--- /dev/null
+++ b/homeassistant/components/watson_iot/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "watson_iot",
+ "name": "Watson iot",
+ "documentation": "https://www.home-assistant.io/components/watson_iot",
+ "requirements": [
+ "ibmiotf==0.3.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/waze_travel_time/manifest.json b/homeassistant/components/waze_travel_time/manifest.json
new file mode 100644
index 00000000000..64b384356ce
--- /dev/null
+++ b/homeassistant/components/waze_travel_time/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "waze_travel_time",
+ "name": "Waze travel time",
+ "documentation": "https://www.home-assistant.io/components/waze_travel_time",
+ "requirements": [
+ "WazeRouteCalculator==0.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py
index 96a4c747293..282637b1507 100644
--- a/homeassistant/components/waze_travel_time/sensor.py
+++ b/homeassistant/components/waze_travel_time/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Waze travel time sensor.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.waze_travel_time/
-"""
+"""Support for Waze travel time sensor."""
from datetime import timedelta
import logging
@@ -18,8 +13,6 @@ from homeassistant.helpers import location
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['WazeRouteCalculator==0.9']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DURATION = 'duration'
diff --git a/homeassistant/components/weather/manifest.json b/homeassistant/components/weather/manifest.json
new file mode 100644
index 00000000000..7008c033f95
--- /dev/null
+++ b/homeassistant/components/weather/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "weather",
+ "name": "Weather",
+ "documentation": "https://www.home-assistant.io/components/weather",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py
index 59be3ab1890..7d8dda06e4d 100644
--- a/homeassistant/components/webhook/__init__.py
+++ b/homeassistant/components/webhook/__init__.py
@@ -12,8 +12,6 @@ from homeassistant.components.http.view import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['http']
-
DOMAIN = 'webhook'
URL_WEBHOOK_PATH = "/api/webhook/{webhook_id}"
diff --git a/homeassistant/components/webhook/manifest.json b/homeassistant/components/webhook/manifest.json
new file mode 100644
index 00000000000..384e61aed2a
--- /dev/null
+++ b/homeassistant/components/webhook/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "webhook",
+ "name": "Webhook",
+ "documentation": "https://www.home-assistant.io/components/webhook",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/weblink/manifest.json b/homeassistant/components/weblink/manifest.json
new file mode 100644
index 00000000000..7c30ad6c5d3
--- /dev/null
+++ b/homeassistant/components/weblink/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "weblink",
+ "name": "Weblink",
+ "documentation": "https://www.home-assistant.io/components/weblink",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json
new file mode 100644
index 00000000000..4dd2f92628d
--- /dev/null
+++ b/homeassistant/components/webostv/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "webostv",
+ "name": "Webostv",
+ "documentation": "https://www.home-assistant.io/components/webostv",
+ "requirements": [
+ "pylgtv==0.1.9",
+ "websockets==6.0"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py
index 35c3c456680..fa62e29f233 100644
--- a/homeassistant/components/webostv/media_player.py
+++ b/homeassistant/components/webostv/media_player.py
@@ -21,8 +21,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script
-REQUIREMENTS = ['pylgtv==0.1.9', 'websockets==6.0']
-
_CONFIGURING = {} # type: Dict[str, str]
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py
index 5887586df65..d8b1d04f8bf 100644
--- a/homeassistant/components/webostv/notify.py
+++ b/homeassistant/components/webostv/notify.py
@@ -8,8 +8,6 @@ from homeassistant.components.notify import (
ATTR_DATA, BaseNotificationService, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_FILENAME, CONF_HOST, CONF_ICON)
-REQUIREMENTS = ['pylgtv==0.1.9']
-
_LOGGER = logging.getLogger(__name__)
WEBOSTV_CONFIG_FILE = 'webostv.conf'
diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py
index 6c4935b9d95..6bb4ea9c1c4 100644
--- a/homeassistant/components/websocket_api/__init__.py
+++ b/homeassistant/components/websocket_api/__init__.py
@@ -43,5 +43,5 @@ def async_register_command(hass, command_or_handler, handler=None,
async def async_setup(hass, config):
"""Initialize the websocket API."""
hass.http.register_view(http.WebsocketAPIView)
- commands.async_register_commands(hass)
+ commands.async_register_commands(hass, async_register_command)
return True
diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py
index 32bbd90aad1..d9834758c80 100644
--- a/homeassistant/components/websocket_api/commands.py
+++ b/homeassistant/components/websocket_api/commands.py
@@ -14,16 +14,15 @@ from . import const, decorators, messages
@callback
-def async_register_commands(hass):
+def async_register_commands(hass, async_reg):
"""Register commands."""
- async_reg = hass.components.websocket_api.async_register_command
- async_reg(handle_subscribe_events)
- async_reg(handle_unsubscribe_events)
- async_reg(handle_call_service)
- async_reg(handle_get_states)
- async_reg(handle_get_services)
- async_reg(handle_get_config)
- async_reg(handle_ping)
+ async_reg(hass, handle_subscribe_events)
+ async_reg(hass, handle_unsubscribe_events)
+ async_reg(hass, handle_call_service)
+ async_reg(hass, handle_get_states)
+ async_reg(hass, handle_get_services)
+ async_reg(hass, handle_get_config)
+ async_reg(hass, handle_ping)
def pong_message(iden):
diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py
index 4c3e0d564dc..53ca680c4c9 100644
--- a/homeassistant/components/websocket_api/const.py
+++ b/homeassistant/components/websocket_api/const.py
@@ -24,3 +24,6 @@ CANCELLATION_ERRORS = (asyncio.CancelledError, futures.CancelledError)
# Event types
SIGNAL_WEBSOCKET_CONNECTED = 'websocket_connected'
SIGNAL_WEBSOCKET_DISCONNECTED = 'websocket_disconnected'
+
+# Data used to store the current connection list
+DATA_CONNECTIONS = DOMAIN + '.connections'
diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py
index 85cb256df90..0fc446390b7 100644
--- a/homeassistant/components/websocket_api/http.py
+++ b/homeassistant/components/websocket_api/http.py
@@ -15,7 +15,8 @@ from homeassistant.helpers.json import JSONEncoder
from .const import (
MAX_PENDING_MSG, CANCELLATION_ERRORS, URL, ERR_UNKNOWN_ERROR,
- SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED)
+ SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED,
+ DATA_CONNECTIONS)
from .auth import AuthPhase, auth_required_message
from .error import Disconnect
from .messages import error_message
@@ -53,7 +54,8 @@ class WebSocketHandler:
async def _writer(self):
"""Write outgoing messages."""
# Exceptions if Socket disconnected or cancelled by connection handler
- with suppress(RuntimeError, *CANCELLATION_ERRORS):
+ with suppress(RuntimeError, ConnectionResetError,
+ *CANCELLATION_ERRORS):
while not self.wsock.closed:
message = await self._to_write.get()
if message is None:
@@ -144,6 +146,8 @@ class WebSocketHandler:
self._logger.debug("Received %s", msg)
connection = await auth.async_handle(msg)
+ self.hass.data[DATA_CONNECTIONS] = \
+ self.hass.data.get(DATA_CONNECTIONS, 0) + 1
self.hass.helpers.dispatcher.async_dispatcher_send(
SIGNAL_WEBSOCKET_CONNECTED)
@@ -196,6 +200,7 @@ class WebSocketHandler:
else:
self._logger.warning("Disconnected: %s", disconnect_warn)
+ self.hass.data[DATA_CONNECTIONS] -= 1
self.hass.helpers.dispatcher.async_dispatcher_send(
SIGNAL_WEBSOCKET_DISCONNECTED)
diff --git a/homeassistant/components/websocket_api/manifest.json b/homeassistant/components/websocket_api/manifest.json
new file mode 100644
index 00000000000..bc630b2947f
--- /dev/null
+++ b/homeassistant/components/websocket_api/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "websocket_api",
+ "name": "Websocket api",
+ "documentation": "https://www.home-assistant.io/components/websocket_api",
+ "requirements": [],
+ "dependencies": [
+ "http"
+ ],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py
index fd9108c0513..b43e356b9ce 100644
--- a/homeassistant/components/websocket_api/sensor.py
+++ b/homeassistant/components/websocket_api/sensor.py
@@ -3,7 +3,9 @@
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
-from .const import SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED
+from .const import (
+ SIGNAL_WEBSOCKET_CONNECTED, SIGNAL_WEBSOCKET_DISCONNECTED,
+ DATA_CONNECTIONS)
async def async_setup_platform(
@@ -11,12 +13,6 @@ async def async_setup_platform(
"""Set up the API streams platform."""
entity = APICount()
- # pylint: disable=protected-access
- hass.helpers.dispatcher.async_dispatcher_connect(
- SIGNAL_WEBSOCKET_CONNECTED, entity._increment)
- hass.helpers.dispatcher.async_dispatcher_connect(
- SIGNAL_WEBSOCKET_DISCONNECTED, entity._decrement)
-
async_add_entities([entity])
@@ -25,7 +21,15 @@ class APICount(Entity):
def __init__(self):
"""Initialize the API count."""
- self.count = 0
+ self.count = None
+
+ async def async_added_to_hass(self):
+ """Added to hass."""
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ SIGNAL_WEBSOCKET_CONNECTED, self._update_count)
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ SIGNAL_WEBSOCKET_DISCONNECTED, self._update_count)
+ self._update_count()
@property
def name(self):
@@ -43,11 +47,6 @@ class APICount(Entity):
return "clients"
@callback
- def _increment(self):
- self.count += 1
- self.async_schedule_update_ha_state()
-
- @callback
- def _decrement(self):
- self.count -= 1
+ def _update_count(self):
+ self.count = self.hass.data.get(DATA_CONNECTIONS, 0)
self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/websocket_api/services.yaml b/homeassistant/components/websocket_api/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py
index 709b3ec8672..d921075bc1a 100644
--- a/homeassistant/components/wemo/__init__.py
+++ b/homeassistant/components/wemo/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import discovery
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
-REQUIREMENTS = ['pywemo==0.4.34']
-
DOMAIN = 'wemo'
# Mapping from Wemo model_name to component.
@@ -166,7 +164,7 @@ def setup(hass, config):
'ssdp_description': url,
}
- discovery.discover(hass, SERVICE_WEMO, discovery_info)
+ discovery_dispatch(SERVICE_WEMO, discovery_info)
_LOGGER.debug("WeMo device discovery has finished")
diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py
index d6c1ad721b9..d7271903498 100644
--- a/homeassistant/components/wemo/binary_sensor.py
+++ b/homeassistant/components/wemo/binary_sensor.py
@@ -8,7 +8,7 @@ import requests
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.exceptions import PlatformNotReady
-DEPENDENCIES = ['wemo']
+from . import SUBSCRIPTION_REGISTRY
_LOGGER = logging.getLogger(__name__)
@@ -66,7 +66,7 @@ class WemoBinarySensor(BinarySensorDevice):
# Define inside async context so we know our event loop
self._update_lock = asyncio.Lock()
- registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
+ registry = SUBSCRIPTION_REGISTRY
await self.hass.async_add_executor_job(registry.register, self.wemo)
registry.on(self.wemo, None, self._subscription_callback)
diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py
index 29a493bf5bc..f635010d98d 100644
--- a/homeassistant/components/wemo/fan.py
+++ b/homeassistant/components/wemo/fan.py
@@ -14,7 +14,8 @@ from homeassistant.components.fan import (
from homeassistant.exceptions import PlatformNotReady
from homeassistant.const import ATTR_ENTITY_ID
-DEPENDENCIES = ['wemo']
+from . import SUBSCRIPTION_REGISTRY
+
SCAN_INTERVAL = timedelta(seconds=10)
DATA_KEY = 'fan.wemo'
@@ -229,7 +230,7 @@ class WemoHumidifier(FanEntity):
# Define inside async context so we know our event loop
self._update_lock = asyncio.Lock()
- registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
+ registry = SUBSCRIPTION_REGISTRY
await self.hass.async_add_executor_job(registry.register, self.wemo)
registry.on(self.wemo, None, self._subscription_callback)
diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py
index e0f729fb165..2429bca8922 100644
--- a/homeassistant/components/wemo/light.py
+++ b/homeassistant/components/wemo/light.py
@@ -13,7 +13,7 @@ from homeassistant.components.light import (
from homeassistant.exceptions import PlatformNotReady
import homeassistant.util.color as color_util
-DEPENDENCIES = ['wemo']
+from . import SUBSCRIPTION_REGISTRY
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
@@ -226,7 +226,7 @@ class WemoDimmer(Light):
# Define inside async context so we know our event loop
self._update_lock = asyncio.Lock()
- registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
+ registry = SUBSCRIPTION_REGISTRY
await self.hass.async_add_executor_job(registry.register, self.wemo)
registry.on(self.wemo, None, self._subscription_callback)
diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json
new file mode 100644
index 00000000000..238be891886
--- /dev/null
+++ b/homeassistant/components/wemo/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "wemo",
+ "name": "Wemo",
+ "documentation": "https://www.home-assistant.io/components/wemo",
+ "requirements": [
+ "pywemo==0.4.34"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@sqldiablo"
+ ]
+}
diff --git a/homeassistant/components/wemo/services.yaml b/homeassistant/components/wemo/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py
index 0a583e49e96..b8967cead3b 100644
--- a/homeassistant/components/wemo/switch.py
+++ b/homeassistant/components/wemo/switch.py
@@ -12,7 +12,8 @@ from homeassistant.util import convert
from homeassistant.const import (
STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN)
-DEPENDENCIES = ['wemo']
+from . import SUBSCRIPTION_REGISTRY
+
SCAN_INTERVAL = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
@@ -198,7 +199,7 @@ class WemoSwitch(SwitchDevice):
# Define inside async context so we know our event loop
self._update_lock = asyncio.Lock()
- registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
+ registry = SUBSCRIPTION_REGISTRY
await self.hass.async_add_job(registry.register, self.wemo)
registry.on(self.wemo, None, self._subscription_callback)
diff --git a/homeassistant/components/whois/manifest.json b/homeassistant/components/whois/manifest.json
new file mode 100644
index 00000000000..dec3e78a503
--- /dev/null
+++ b/homeassistant/components/whois/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "whois",
+ "name": "Whois",
+ "documentation": "https://www.home-assistant.io/components/whois",
+ "requirements": [
+ "python-whois==0.7.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py
index 3685652387a..5a369190c94 100644
--- a/homeassistant/components/whois/sensor.py
+++ b/homeassistant/components/whois/sensor.py
@@ -1,9 +1,4 @@
-"""
-Get WHOIS information for a given host.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.whois/
-"""
+"""Get WHOIS information for a given host."""
from datetime import timedelta
import logging
@@ -14,8 +9,6 @@ from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-whois==0.7.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_DOMAIN = 'domain'
diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py
index 2b03d7711ac..4e25fc4fd0d 100644
--- a/homeassistant/components/wink/__init__.py
+++ b/homeassistant/components/wink/__init__.py
@@ -20,8 +20,6 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import track_time_interval
from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['python-wink==1.10.3', 'pubnubsub-handler==1.0.3']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'wink'
diff --git a/homeassistant/components/wink/alarm_control_panel.py b/homeassistant/components/wink/alarm_control_panel.py
index 73ca9a3cac4..61699c763ce 100644
--- a/homeassistant/components/wink/alarm_control_panel.py
+++ b/homeassistant/components/wink/alarm_control_panel.py
@@ -9,8 +9,6 @@ from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['wink']
-
STATE_ALARM_PRIVACY = 'Private'
diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py
index f3757d7bf39..d8f9163c46e 100644
--- a/homeassistant/components/wink/binary_sensor.py
+++ b/homeassistant/components/wink/binary_sensor.py
@@ -7,8 +7,6 @@ from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['wink']
-
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
'brightness': 'light',
diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py
index f5e75c1fb8d..fd02fdd4ec3 100644
--- a/homeassistant/components/wink/climate.py
+++ b/homeassistant/components/wink/climate.py
@@ -26,8 +26,6 @@ ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_HEAT_ON = 'heat_on'
ATTR_COOL_ON = 'cool_on'
-DEPENDENCIES = ['wink']
-
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
diff --git a/homeassistant/components/wink/cover.py b/homeassistant/components/wink/cover.py
index f4c4841c2a2..b8152adbfda 100644
--- a/homeassistant/components/wink/cover.py
+++ b/homeassistant/components/wink/cover.py
@@ -3,8 +3,6 @@ from homeassistant.components.cover import ATTR_POSITION, CoverDevice
from . import DOMAIN, WinkDevice
-DEPENDENCIES = ['wink']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink cover platform."""
diff --git a/homeassistant/components/wink/fan.py b/homeassistant/components/wink/fan.py
index 52a27eb3c3d..3fb06abc145 100644
--- a/homeassistant/components/wink/fan.py
+++ b/homeassistant/components/wink/fan.py
@@ -9,8 +9,6 @@ from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['wink']
-
SPEED_AUTO = 'auto'
SPEED_LOWEST = 'lowest'
SUPPORTED_FEATURES = SUPPORT_DIRECTION + SUPPORT_SET_SPEED
diff --git a/homeassistant/components/wink/light.py b/homeassistant/components/wink/light.py
index 95747bcc1b2..0da432f7fe3 100644
--- a/homeassistant/components/wink/light.py
+++ b/homeassistant/components/wink/light.py
@@ -8,8 +8,6 @@ from homeassistant.util.color import (
from . import DOMAIN, WinkDevice
-DEPENDENCIES = ['wink']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink lights."""
diff --git a/homeassistant/components/wink/lock.py b/homeassistant/components/wink/lock.py
index 8e6fb9b2805..01e038e9d09 100644
--- a/homeassistant/components/wink/lock.py
+++ b/homeassistant/components/wink/lock.py
@@ -10,8 +10,6 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN, WinkDevice
-DEPENDENCIES = ['wink']
-
_LOGGER = logging.getLogger(__name__)
SERVICE_SET_VACATION_MODE = 'wink_set_lock_vacation_mode'
diff --git a/homeassistant/components/wink/manifest.json b/homeassistant/components/wink/manifest.json
new file mode 100644
index 00000000000..c8951637bde
--- /dev/null
+++ b/homeassistant/components/wink/manifest.json
@@ -0,0 +1,11 @@
+{
+ "domain": "wink",
+ "name": "Wink",
+ "documentation": "https://www.home-assistant.io/components/wink",
+ "requirements": [
+ "pubnubsub-handler==1.0.3",
+ "python-wink==1.10.3"
+ ],
+ "dependencies": ["configurator"],
+ "codeowners": []
+}
diff --git a/homeassistant/components/wink/scene.py b/homeassistant/components/wink/scene.py
index e77402c4d45..d0e03ef0688 100644
--- a/homeassistant/components/wink/scene.py
+++ b/homeassistant/components/wink/scene.py
@@ -7,8 +7,6 @@ from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['wink']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink platform."""
diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py
index 3dfd704d564..b2330894584 100644
--- a/homeassistant/components/wink/sensor.py
+++ b/homeassistant/components/wink/sensor.py
@@ -7,8 +7,6 @@ from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['wink']
-
SENSOR_TYPES = ['temperature', 'humidity', 'balance', 'proximity']
diff --git a/homeassistant/components/wink/switch.py b/homeassistant/components/wink/switch.py
index 6ee777dd1fc..1102087ed2a 100644
--- a/homeassistant/components/wink/switch.py
+++ b/homeassistant/components/wink/switch.py
@@ -5,8 +5,6 @@ from homeassistant.helpers.entity import ToggleEntity
from . import DOMAIN, WinkDevice
-DEPENDENCIES = ['wink']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py
index 28c8cb4d515..61209a8293b 100644
--- a/homeassistant/components/wirelesstag/__init__.py
+++ b/homeassistant/components/wirelesstag/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.dispatcher import (
dispatcher_send)
-REQUIREMENTS = ['wirelesstagpy==0.4.0']
-
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/wirelesstag/binary_sensor.py b/homeassistant/components/wirelesstag/binary_sensor.py
index aefa5ed34a9..7dd3e8df6ca 100644
--- a/homeassistant/components/wirelesstag/binary_sensor.py
+++ b/homeassistant/components/wirelesstag/binary_sensor.py
@@ -14,8 +14,6 @@ from . import (
DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_BINARY_EVENT_UPDATE,
WirelessTagBaseSensor)
-DEPENDENCIES = ['wirelesstag']
-
_LOGGER = logging.getLogger(__name__)
# On means in range, Off means out of range
diff --git a/homeassistant/components/wirelesstag/manifest.json b/homeassistant/components/wirelesstag/manifest.json
new file mode 100644
index 00000000000..c3da00ce951
--- /dev/null
+++ b/homeassistant/components/wirelesstag/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "wirelesstag",
+ "name": "Wirelesstag",
+ "documentation": "https://www.home-assistant.io/components/wirelesstag",
+ "requirements": [
+ "wirelesstagpy==0.4.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py
index ca26e07b985..bba3f1503c9 100644
--- a/homeassistant/components/wirelesstag/sensor.py
+++ b/homeassistant/components/wirelesstag/sensor.py
@@ -12,8 +12,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import (
DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_TAG_UPDATE, WirelessTagBaseSensor)
-DEPENDENCIES = ['wirelesstag']
-
_LOGGER = logging.getLogger(__name__)
SENSOR_TEMPERATURE = 'temperature'
diff --git a/homeassistant/components/wirelesstag/switch.py b/homeassistant/components/wirelesstag/switch.py
index 4a2b64acda1..c909f10c75c 100644
--- a/homeassistant/components/wirelesstag/switch.py
+++ b/homeassistant/components/wirelesstag/switch.py
@@ -9,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN as WIRELESSTAG_DOMAIN, WirelessTagBaseSensor
-DEPENDENCIES = ['wirelesstag']
-
_LOGGER = logging.getLogger(__name__)
ARM_TEMPERATURE = 'temperature'
diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py
index b505e075018..73fa8133c9f 100644
--- a/homeassistant/components/workday/binary_sensor.py
+++ b/homeassistant/components/workday/binary_sensor.py
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_NAME, WEEKDAYS
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['holidays==0.9.10']
-
_LOGGER = logging.getLogger(__name__)
# List of all countries currently supported by holidays
diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json
new file mode 100644
index 00000000000..889ce4059be
--- /dev/null
+++ b/homeassistant/components/workday/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "workday",
+ "name": "Workday",
+ "documentation": "https://www.home-assistant.io/components/workday",
+ "requirements": [
+ "holidays==0.9.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/worldclock/manifest.json b/homeassistant/components/worldclock/manifest.json
new file mode 100644
index 00000000000..2da33f942b8
--- /dev/null
+++ b/homeassistant/components/worldclock/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "worldclock",
+ "name": "Worldclock",
+ "documentation": "https://www.home-assistant.io/components/worldclock",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/worldclock/sensor.py b/homeassistant/components/worldclock/sensor.py
index 6bb5d1fee2e..1fdf97b7088 100644
--- a/homeassistant/components/worldclock/sensor.py
+++ b/homeassistant/components/worldclock/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for showing the time in a different time zone.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.worldclock/
-"""
+"""Support for showing the time in a different time zone."""
import logging
import voluptuous as vol
diff --git a/homeassistant/components/worldtidesinfo/manifest.json b/homeassistant/components/worldtidesinfo/manifest.json
new file mode 100644
index 00000000000..dfc116c97db
--- /dev/null
+++ b/homeassistant/components/worldtidesinfo/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "worldtidesinfo",
+ "name": "Worldtidesinfo",
+ "documentation": "https://www.home-assistant.io/components/worldtidesinfo",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/worldtidesinfo/sensor.py b/homeassistant/components/worldtidesinfo/sensor.py
index 0f7bfeaa900..1a1e349feee 100644
--- a/homeassistant/components/worldtidesinfo/sensor.py
+++ b/homeassistant/components/worldtidesinfo/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the worldtides.info API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.worldtidesinfo/
-"""
+"""Support for the worldtides.info API."""
from datetime import timedelta
import logging
import time
diff --git a/homeassistant/components/worxlandroid/manifest.json b/homeassistant/components/worxlandroid/manifest.json
new file mode 100644
index 00000000000..3e7c626ddd0
--- /dev/null
+++ b/homeassistant/components/worxlandroid/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "worxlandroid",
+ "name": "Worxlandroid",
+ "documentation": "https://www.home-assistant.io/components/worxlandroid",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py
index be5c8452d88..fa4fcc96c12 100644
--- a/homeassistant/components/worxlandroid/sensor.py
+++ b/homeassistant/components/worxlandroid/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Worx Landroid mower.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.worxlandroid/
-"""
+"""Support for Worx Landroid mower."""
import logging
import asyncio
diff --git a/homeassistant/components/wsdot/manifest.json b/homeassistant/components/wsdot/manifest.json
new file mode 100644
index 00000000000..c778ed1049f
--- /dev/null
+++ b/homeassistant/components/wsdot/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "wsdot",
+ "name": "Wsdot",
+ "documentation": "https://www.home-assistant.io/components/wsdot",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py
index 4e53a2c17c4..3c3e9300a02 100644
--- a/homeassistant/components/wsdot/sensor.py
+++ b/homeassistant/components/wsdot/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Washington State Department of Transportation (WSDOT) data.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.wsdot/
-"""
+"""Support for Washington State Department of Transportation (WSDOT) data."""
import logging
import re
from datetime import datetime, timezone, timedelta
diff --git a/homeassistant/components/wunderground/manifest.json b/homeassistant/components/wunderground/manifest.json
new file mode 100644
index 00000000000..d14c9db419a
--- /dev/null
+++ b/homeassistant/components/wunderground/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "wunderground",
+ "name": "Wunderground",
+ "documentation": "https://www.home-assistant.io/components/wunderground",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py
index 74a4c2089b2..7ad1a6fd75a 100644
--- a/homeassistant/components/wunderground/sensor.py
+++ b/homeassistant/components/wunderground/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for WUnderground weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.wunderground/
-"""
+"""Support for WUnderground weather service."""
import asyncio
from datetime import timedelta
import logging
diff --git a/homeassistant/components/wunderlist/__init__.py b/homeassistant/components/wunderlist/__init__.py
index d67cf089b5e..5c85c746826 100644
--- a/homeassistant/components/wunderlist/__init__.py
+++ b/homeassistant/components/wunderlist/__init__.py
@@ -7,8 +7,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_NAME, CONF_ACCESS_TOKEN)
-REQUIREMENTS = ['wunderpy2==0.1.6']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'wunderlist'
diff --git a/homeassistant/components/wunderlist/manifest.json b/homeassistant/components/wunderlist/manifest.json
new file mode 100644
index 00000000000..505447f454c
--- /dev/null
+++ b/homeassistant/components/wunderlist/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "wunderlist",
+ "name": "Wunderlist",
+ "documentation": "https://www.home-assistant.io/components/wunderlist",
+ "requirements": [
+ "wunderpy2==0.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py
index 9618a13a1a9..6c8c5f3fe6f 100644
--- a/homeassistant/components/x10/light.py
+++ b/homeassistant/components/x10/light.py
@@ -1,9 +1,4 @@
-"""
-Support for X10 lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.x10/
-"""
+"""Support for X10 lights."""
import logging
from subprocess import check_output, CalledProcessError, STDOUT
diff --git a/homeassistant/components/x10/manifest.json b/homeassistant/components/x10/manifest.json
new file mode 100644
index 00000000000..2fbe16a6e7a
--- /dev/null
+++ b/homeassistant/components/x10/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "x10",
+ "name": "X10",
+ "documentation": "https://www.home-assistant.io/components/x10",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/xbox_live/manifest.json b/homeassistant/components/xbox_live/manifest.json
new file mode 100644
index 00000000000..0d80ce770ce
--- /dev/null
+++ b/homeassistant/components/xbox_live/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "xbox_live",
+ "name": "Xbox live",
+ "documentation": "https://www.home-assistant.io/components/xbox_live",
+ "requirements": [
+ "xboxapi==0.1.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py
index 9670b4b2f9c..874c1629694 100644
--- a/homeassistant/components/xbox_live/sensor.py
+++ b/homeassistant/components/xbox_live/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for Xbox Live account status.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.xbox_live/
-"""
+"""Sensor for Xbox Live account status."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_API_KEY, STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['xboxapi==0.1.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_XUID = 'xuid'
diff --git a/homeassistant/components/xeoma/camera.py b/homeassistant/components/xeoma/camera.py
index 74532a935fc..60f7ab2c972 100644
--- a/homeassistant/components/xeoma/camera.py
+++ b/homeassistant/components/xeoma/camera.py
@@ -1,9 +1,4 @@
-"""
-Support for Xeoma Cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.xeoma/
-"""
+"""Support for Xeoma Cameras."""
import logging
import voluptuous as vol
@@ -13,8 +8,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
-REQUIREMENTS = ['pyxeoma==1.4.1']
-
_LOGGER = logging.getLogger(__name__)
CONF_CAMERAS = 'cameras'
diff --git a/homeassistant/components/xeoma/manifest.json b/homeassistant/components/xeoma/manifest.json
new file mode 100644
index 00000000000..ee8ed2f6de3
--- /dev/null
+++ b/homeassistant/components/xeoma/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "xeoma",
+ "name": "Xeoma",
+ "documentation": "https://www.home-assistant.io/components/xeoma",
+ "requirements": [
+ "pyxeoma==1.4.1"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/xfinity/device_tracker.py b/homeassistant/components/xfinity/device_tracker.py
index 04702355de7..bdde650091d 100644
--- a/homeassistant/components/xfinity/device_tracker.py
+++ b/homeassistant/components/xfinity/device_tracker.py
@@ -9,8 +9,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
-REQUIREMENTS = ['xfinity-gateway==0.0.4']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = '10.0.0.1'
diff --git a/homeassistant/components/xfinity/manifest.json b/homeassistant/components/xfinity/manifest.json
new file mode 100644
index 00000000000..71750ccf088
--- /dev/null
+++ b/homeassistant/components/xfinity/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "xfinity",
+ "name": "Xfinity",
+ "documentation": "https://www.home-assistant.io/components/xfinity",
+ "requirements": [
+ "xfinity-gateway==0.0.4"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@cisasteelersfan"
+ ]
+}
diff --git a/homeassistant/components/xiaomi/camera.py b/homeassistant/components/xiaomi/camera.py
index b0acf50ec8c..e541936ef0e 100644
--- a/homeassistant/components/xiaomi/camera.py
+++ b/homeassistant/components/xiaomi/camera.py
@@ -1,9 +1,4 @@
-"""
-This component provides support for Xiaomi Cameras.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.xiaomi/
-"""
+"""This component provides support for Xiaomi Cameras."""
import asyncio
import logging
@@ -16,7 +11,6 @@ from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
-DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
DEFAULT_BRAND = 'Xiaomi Home Camera'
diff --git a/homeassistant/components/xiaomi/device_tracker.py b/homeassistant/components/xiaomi/device_tracker.py
index 12e64b724dd..6c588271c9b 100644
--- a/homeassistant/components/xiaomi/device_tracker.py
+++ b/homeassistant/components/xiaomi/device_tracker.py
@@ -1,9 +1,4 @@
-"""
-Support for Xiaomi Mi routers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/device_tracker.xiaomi/
-"""
+"""Support for Xiaomi Mi routers."""
import logging
import requests
diff --git a/homeassistant/components/xiaomi/manifest.json b/homeassistant/components/xiaomi/manifest.json
new file mode 100644
index 00000000000..d3587100501
--- /dev/null
+++ b/homeassistant/components/xiaomi/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "xiaomi",
+ "name": "Xiaomi",
+ "documentation": "https://www.home-assistant.io/components/xiaomi",
+ "requirements": [],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": []
+}
diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py
index 9b113170f8a..22a8ec95c33 100644
--- a/homeassistant/components/xiaomi_aqara/__init__.py
+++ b/homeassistant/components/xiaomi_aqara/__init__.py
@@ -16,8 +16,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['PyXiaomiGateway==0.12.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_GW_MAC = 'gw_mac'
diff --git a/homeassistant/components/xiaomi_aqara/manifest.json b/homeassistant/components/xiaomi_aqara/manifest.json
new file mode 100644
index 00000000000..a79f2960497
--- /dev/null
+++ b/homeassistant/components/xiaomi_aqara/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "xiaomi_aqara",
+ "name": "Xiaomi aqara",
+ "documentation": "https://www.home-assistant.io/components/xiaomi_aqara",
+ "requirements": [
+ "PyXiaomiGateway==0.12.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@danielhiversen",
+ "@syssi"
+ ]
+}
diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py
index e7ea9fbbb40..5e5485364df 100644
--- a/homeassistant/components/xiaomi_miio/device_tracker.py
+++ b/homeassistant/components/xiaomi_miio/device_tracker.py
@@ -8,8 +8,6 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST, CONF_TOKEN
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py
index 51d4780160d..ea00cd6d95e 100644
--- a/homeassistant/components/xiaomi_miio/fan.py
+++ b/homeassistant/components/xiaomi_miio/fan.py
@@ -13,8 +13,6 @@ from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_TOKEN,
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Xiaomi Miio Device'
diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py
index ec07a557342..fa853d1f83d 100644
--- a/homeassistant/components/xiaomi_miio/light.py
+++ b/homeassistant/components/xiaomi_miio/light.py
@@ -17,8 +17,6 @@ from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.util import color, dt
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Xiaomi Philips Light'
diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json
new file mode 100644
index 00000000000..d7e0d0d732e
--- /dev/null
+++ b/homeassistant/components/xiaomi_miio/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "xiaomi_miio",
+ "name": "Xiaomi miio",
+ "documentation": "https://www.home-assistant.io/components/xiaomi_miio",
+ "requirements": [
+ "construct==2.9.45",
+ "python-miio==0.4.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@rytilahti",
+ "@syssi"
+ ]
+}
diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py
index 450279c1825..7cb0cd68439 100644
--- a/homeassistant/components/xiaomi_miio/remote.py
+++ b/homeassistant/components/xiaomi_miio/remote.py
@@ -17,8 +17,6 @@ from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
SERVICE_LEARN = 'xiaomi_miio_learn_command'
diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py
index 41d3ce65b13..be500f665f4 100644
--- a/homeassistant/components/xiaomi_miio/sensor.py
+++ b/homeassistant/components/xiaomi_miio/sensor.py
@@ -9,8 +9,6 @@ from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Xiaomi Miio Sensor'
diff --git a/homeassistant/components/xiaomi_miio/services.yaml b/homeassistant/components/xiaomi_miio/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py
index d1acce02e47..91924c82821 100644
--- a/homeassistant/components/xiaomi_miio/switch.py
+++ b/homeassistant/components/xiaomi_miio/switch.py
@@ -12,8 +12,6 @@ from homeassistant.const import (
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Xiaomi Miio Switch'
diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py
index c7f69a40330..ce527d41e25 100644
--- a/homeassistant/components/xiaomi_miio/vacuum.py
+++ b/homeassistant/components/xiaomi_miio/vacuum.py
@@ -16,8 +16,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['python-miio==0.4.5', 'construct==2.9.45']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Xiaomi Vacuum cleaner'
@@ -303,7 +301,7 @@ class MiroboVacuum(StateVacuumDevice):
async def async_start(self):
"""Start or resume the cleaning task."""
await self._try_command(
- "Unable to start the vacuum: %s", self._vacuum.start)
+ "Unable to start the vacuum: %s", self._vacuum.resume_or_start)
async def async_pause(self):
"""Pause the cleaning task."""
diff --git a/homeassistant/components/xiaomi_tv/manifest.json b/homeassistant/components/xiaomi_tv/manifest.json
new file mode 100644
index 00000000000..221532c1c8d
--- /dev/null
+++ b/homeassistant/components/xiaomi_tv/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "xiaomi_tv",
+ "name": "Xiaomi tv",
+ "documentation": "https://www.home-assistant.io/components/xiaomi_tv",
+ "requirements": [
+ "pymitv==1.4.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fattdev"
+ ]
+}
diff --git a/homeassistant/components/xiaomi_tv/media_player.py b/homeassistant/components/xiaomi_tv/media_player.py
index e3b25c3c31f..862ed3bcc39 100644
--- a/homeassistant/components/xiaomi_tv/media_player.py
+++ b/homeassistant/components/xiaomi_tv/media_player.py
@@ -1,9 +1,4 @@
-"""
-Add support for the Xiaomi TVs.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/xiaomi_tv/
-"""
+"""Add support for the Xiaomi TVs."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.media_player.const import (
from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pymitv==1.4.3']
-
DEFAULT_NAME = "Xiaomi TV"
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/xmpp/manifest.json b/homeassistant/components/xmpp/manifest.json
new file mode 100644
index 00000000000..d8e4e5c4da6
--- /dev/null
+++ b/homeassistant/components/xmpp/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "xmpp",
+ "name": "Xmpp",
+ "documentation": "https://www.home-assistant.io/components/xmpp",
+ "requirements": [
+ "slixmpp==1.4.2"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@fabaff"
+ ]
+}
diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py
index 5a14046bd41..79e6edafdb4 100644
--- a/homeassistant/components/xmpp/notify.py
+++ b/homeassistant/components/xmpp/notify.py
@@ -1,9 +1,4 @@
-"""
-Jabber (XMPP) notification service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.xmpp/
-"""
+"""Jabber (XMPP) notification service."""
from concurrent.futures import TimeoutError as FutTimeoutError
import logging
import mimetypes
@@ -22,8 +17,6 @@ import homeassistant.helpers.template as template_helper
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService)
-REQUIREMENTS = ['slixmpp==1.4.2']
-
_LOGGER = logging.getLogger(__name__)
ATTR_DATA = 'data'
diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py
index f67eb8fd15a..7e245dc8135 100644
--- a/homeassistant/components/xs1/__init__.py
+++ b/homeassistant/components/xs1/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-REQUIREMENTS = ['xs1-api-client==2.3.5']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'xs1'
diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py
index 080b87c1346..1d12fcc90fa 100644
--- a/homeassistant/components/xs1/climate.py
+++ b/homeassistant/components/xs1/climate.py
@@ -8,7 +8,6 @@ from homeassistant.const import ATTR_TEMPERATURE
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
-DEPENDENCIES = ['xs1']
_LOGGER = logging.getLogger(__name__)
MIN_TEMP = 8
diff --git a/homeassistant/components/xs1/manifest.json b/homeassistant/components/xs1/manifest.json
new file mode 100644
index 00000000000..4ee13acf647
--- /dev/null
+++ b/homeassistant/components/xs1/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "xs1",
+ "name": "Xs1",
+ "documentation": "https://www.home-assistant.io/components/xs1",
+ "requirements": [
+ "xs1-api-client==2.3.5"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py
index f5fdcf1fb34..150c2da1f37 100644
--- a/homeassistant/components/xs1/sensor.py
+++ b/homeassistant/components/xs1/sensor.py
@@ -5,7 +5,6 @@ from homeassistant.helpers.entity import Entity
from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity
-DEPENDENCIES = ['xs1']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/xs1/switch.py b/homeassistant/components/xs1/switch.py
index d8b344fc716..2513d888dd8 100644
--- a/homeassistant/components/xs1/switch.py
+++ b/homeassistant/components/xs1/switch.py
@@ -7,8 +7,6 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, XS1DeviceEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['xs1']
-
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py
index 67b74033442..ac1b220b120 100755
--- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py
+++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py
@@ -1,9 +1,4 @@
-"""
-Yale Smart Alarm client for interacting with the Yale Smart Alarm System API.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/alarm_control_panel.yale_smart_alarm
-"""
+"""Component for interacting with the Yale Smart Alarm System API."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['yalesmartalarmclient==0.1.6']
-
CONF_AREA_ID = 'area_id'
DEFAULT_NAME = 'Yale Smart Alarm'
diff --git a/homeassistant/components/yale_smart_alarm/manifest.json b/homeassistant/components/yale_smart_alarm/manifest.json
new file mode 100644
index 00000000000..7b786c7bf7c
--- /dev/null
+++ b/homeassistant/components/yale_smart_alarm/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "yale_smart_alarm",
+ "name": "Yale smart alarm",
+ "documentation": "https://www.home-assistant.io/components/yale_smart_alarm",
+ "requirements": [
+ "yalesmartalarmclient==0.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/yamaha/manifest.json b/homeassistant/components/yamaha/manifest.json
new file mode 100644
index 00000000000..5a277fc7ce8
--- /dev/null
+++ b/homeassistant/components/yamaha/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "yamaha",
+ "name": "Yamaha",
+ "documentation": "https://www.home-assistant.io/components/yamaha",
+ "requirements": [
+ "rxv==0.6.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/yamaha/media_player.py b/homeassistant/components/yamaha/media_player.py
index f652d95e713..6ccbb1b93db 100644
--- a/homeassistant/components/yamaha/media_player.py
+++ b/homeassistant/components/yamaha/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Yamaha Receivers.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.yamaha/
-"""
+"""Support for Yamaha Receivers."""
import logging
import requests
@@ -23,8 +18,6 @@ from homeassistant.const import (
STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['rxv==0.6.0']
-
_LOGGER = logging.getLogger(__name__)
ATTR_ENABLED = 'enabled'
diff --git a/homeassistant/components/yamaha/services.yaml b/homeassistant/components/yamaha/services.yaml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/homeassistant/components/yamaha_musiccast/manifest.json b/homeassistant/components/yamaha_musiccast/manifest.json
new file mode 100644
index 00000000000..7769026e092
--- /dev/null
+++ b/homeassistant/components/yamaha_musiccast/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "yamaha_musiccast",
+ "name": "Yamaha musiccast",
+ "documentation": "https://www.home-assistant.io/components/yamaha_musiccast",
+ "requirements": [
+ "pymusiccast==0.1.6"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@jalmeroth"
+ ]
+}
diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py
index 6aa06b604c5..cfca4ae52f3 100644
--- a/homeassistant/components/yamaha_musiccast/media_player.py
+++ b/homeassistant/components/yamaha_musiccast/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for Yamaha MusicCast Receivers.
-
-For more details about this platform, please refer to the documentation at
-https://www.home-assistant.io/components/media_player.yamaha_musiccast/
-"""
+"""Support for Yamaha MusicCast Receivers."""
import logging
import voluptuous as vol
@@ -20,8 +15,6 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
-REQUIREMENTS = ['pymusiccast==0.1.6']
-
_LOGGER = logging.getLogger(__name__)
SUPPORTED_FEATURES = (
diff --git a/homeassistant/components/yandextts/manifest.json b/homeassistant/components/yandextts/manifest.json
new file mode 100644
index 00000000000..7f622a1e25f
--- /dev/null
+++ b/homeassistant/components/yandextts/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "yandextts",
+ "name": "Yandextts",
+ "documentation": "https://www.home-assistant.io/components/yandextts",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/yandextts/tts.py b/homeassistant/components/yandextts/tts.py
index e60b890e84f..e08f44d1974 100644
--- a/homeassistant/components/yandextts/tts.py
+++ b/homeassistant/components/yandextts/tts.py
@@ -1,9 +1,4 @@
-"""
-Support for the yandex speechkit tts service.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/tts/yandextts/
-"""
+"""Support for the yandex speechkit tts service."""
import asyncio
import logging
diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py
index 0cb9d41fe4b..8c2c9c957c6 100644
--- a/homeassistant/components/yeelight/__init__.py
+++ b/homeassistant/components/yeelight/__init__.py
@@ -16,13 +16,11 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval
-REQUIREMENTS = ['yeelight==0.4.4']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = "yeelight"
DATA_YEELIGHT = DOMAIN
-DATA_UPDATED = '{}_data_updated'.format(DOMAIN)
+DATA_UPDATED = 'yeelight_{}_data_updated'
DEFAULT_NAME = 'Yeelight'
DEFAULT_TRANSITION = 350
@@ -274,4 +272,4 @@ class YeelightDevice:
_LOGGER.error("Unable to update bulb status: %s", ex)
self._available = False
- dispatcher_send(self._hass, DATA_UPDATED, self._ipaddr)
+ dispatcher_send(self._hass, DATA_UPDATED.format(self._ipaddr))
diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py
index d39af08f768..b2a61090a30 100644
--- a/homeassistant/components/yeelight/binary_sensor.py
+++ b/homeassistant/components/yeelight/binary_sensor.py
@@ -6,8 +6,6 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import DATA_YEELIGHT, DATA_UPDATED
-DEPENDENCIES = ['yeelight']
-
_LOGGER = logging.getLogger(__name__)
@@ -31,14 +29,15 @@ class YeelightNightlightModeSensor(BinarySensorDevice):
self._device = device
@callback
- def _schedule_immediate_update(self, ipaddr):
- if ipaddr == self._device.ipaddr:
- self.async_schedule_update_ha_state()
+ def _schedule_immediate_update(self):
+ self.async_schedule_update_ha_state()
async def async_added_to_hass(self):
"""Handle entity which will be added."""
async_dispatcher_connect(
- self.hass, DATA_UPDATED, self._schedule_immediate_update
+ self.hass,
+ DATA_UPDATED.format(self._device.ipaddr),
+ self._schedule_immediate_update
)
@property
diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py
index 74796a524b0..fa62bdc35d7 100644
--- a/homeassistant/components/yeelight/light.py
+++ b/homeassistant/components/yeelight/light.py
@@ -22,8 +22,6 @@ from . import (
YEELIGHT_FLOW_TRANSITION_SCHEMA, ACTION_RECOVER, CONF_FLOW_PARAMS,
ATTR_ACTION, ATTR_COUNT)
-DEPENDENCIES = ['yeelight']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_YEELIGHT = (SUPPORT_BRIGHTNESS |
@@ -212,14 +210,15 @@ class YeelightLight(Light):
self._custom_effects = {}
@callback
- def _schedule_immediate_update(self, ipaddr):
- if ipaddr == self.device.ipaddr:
- self.async_schedule_update_ha_state(True)
+ def _schedule_immediate_update(self):
+ self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Handle entity which will be added."""
async_dispatcher_connect(
- self.hass, DATA_UPDATED, self._schedule_immediate_update
+ self.hass,
+ DATA_UPDATED.format(self._device.ipaddr),
+ self._schedule_immediate_update
)
@property
diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json
new file mode 100644
index 00000000000..061d2b065c4
--- /dev/null
+++ b/homeassistant/components/yeelight/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "yeelight",
+ "name": "Yeelight",
+ "documentation": "https://www.home-assistant.io/components/yeelight",
+ "requirements": [
+ "yeelight==0.5.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@rytilahti",
+ "@zewelor"
+ ]
+}
diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py
index 2250a85c55c..a8636a280f5 100644
--- a/homeassistant/components/yeelightsunflower/light.py
+++ b/homeassistant/components/yeelightsunflower/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Yeelight Sunflower color bulbs (not Yeelight Blue or WiFi).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.yeelightsunflower/
-"""
+"""Support for Yeelight Sunflower color bulbs (not Yeelight Blue or WiFi)."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.light import (
from homeassistant.const import CONF_HOST
import homeassistant.util.color as color_util
-REQUIREMENTS = ['yeelightsunflower==0.0.10']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_YEELIGHT_SUNFLOWER = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR)
diff --git a/homeassistant/components/yeelightsunflower/manifest.json b/homeassistant/components/yeelightsunflower/manifest.json
new file mode 100644
index 00000000000..1a75472b801
--- /dev/null
+++ b/homeassistant/components/yeelightsunflower/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "yeelightsunflower",
+ "name": "Yeelightsunflower",
+ "documentation": "https://www.home-assistant.io/components/yeelightsunflower",
+ "requirements": [
+ "yeelightsunflower==0.0.10"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@lindsaymarkward"
+ ]
+}
diff --git a/homeassistant/components/yessssms/manifest.json b/homeassistant/components/yessssms/manifest.json
new file mode 100644
index 00000000000..103a9fce31e
--- /dev/null
+++ b/homeassistant/components/yessssms/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "yessssms",
+ "name": "Yessssms",
+ "documentation": "https://www.home-assistant.io/components/yessssms",
+ "requirements": [
+ "YesssSMS==0.2.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@flowolf"
+ ]
+}
diff --git a/homeassistant/components/yessssms/notify.py b/homeassistant/components/yessssms/notify.py
index 529aa4e7b6e..5c3af591a12 100644
--- a/homeassistant/components/yessssms/notify.py
+++ b/homeassistant/components/yessssms/notify.py
@@ -1,9 +1,4 @@
-"""
-Support for the YesssSMS platform.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/notify.yessssms/
-"""
+"""Support for the YesssSMS platform."""
import logging
import voluptuous as vol
@@ -14,8 +9,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (PLATFORM_SCHEMA,
BaseNotificationService)
-REQUIREMENTS = ['YesssSMS==0.2.3']
-
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/yi/camera.py b/homeassistant/components/yi/camera.py
index f82c8c38129..0dbb42c384e 100644
--- a/homeassistant/components/yi/camera.py
+++ b/homeassistant/components/yi/camera.py
@@ -1,9 +1,4 @@
-"""
-This component provides support for Xiaomi Cameras (HiSilicon Hi3518e V200).
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/camera.yi/
-"""
+"""Support for Xiaomi Cameras (HiSilicon Hi3518e V200)."""
import asyncio
import logging
@@ -17,8 +12,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.exceptions import PlatformNotReady
-REQUIREMENTS = ['aioftp==0.12.0']
-DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
DEFAULT_BRAND = 'YI Home Camera'
diff --git a/homeassistant/components/yi/manifest.json b/homeassistant/components/yi/manifest.json
new file mode 100644
index 00000000000..bb7fbf55cbc
--- /dev/null
+++ b/homeassistant/components/yi/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "yi",
+ "name": "Yi",
+ "documentation": "https://www.home-assistant.io/components/yi",
+ "requirements": [
+ "aioftp==0.12.0"
+ ],
+ "dependencies": [
+ "ffmpeg"
+ ],
+ "codeowners": [
+ "@bachya"
+ ]
+}
diff --git a/homeassistant/components/yr/manifest.json b/homeassistant/components/yr/manifest.json
new file mode 100644
index 00000000000..ec12f6cdac4
--- /dev/null
+++ b/homeassistant/components/yr/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "yr",
+ "name": "Yr",
+ "documentation": "https://www.home-assistant.io/components/yr",
+ "requirements": [
+ "xmltodict==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/yr/sensor.py b/homeassistant/components/yr/sensor.py
index 665c482f050..c9f57abf5d9 100644
--- a/homeassistant/components/yr/sensor.py
+++ b/homeassistant/components/yr/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for Yr.no weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.yr/
-"""
+"""Support for Yr.no weather service."""
import asyncio
import logging
@@ -25,8 +20,6 @@ from homeassistant.helpers.event import (async_track_utc_time_change,
async_call_later)
from homeassistant.util import dt as dt_util
-REQUIREMENTS = ['xmltodict==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Weather forecast from met.no, delivered by the Norwegian " \
diff --git a/homeassistant/components/yweather/manifest.json b/homeassistant/components/yweather/manifest.json
new file mode 100644
index 00000000000..c3048601595
--- /dev/null
+++ b/homeassistant/components/yweather/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "yweather",
+ "name": "Yweather",
+ "documentation": "https://www.home-assistant.io/components/yweather",
+ "requirements": [
+ "yahooweather==0.10"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/yweather/sensor.py b/homeassistant/components/yweather/sensor.py
index 349ee2c7aae..fc49d79d110 100644
--- a/homeassistant/components/yweather/sensor.py
+++ b/homeassistant/components/yweather/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for the Yahoo! Weather service.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.yweather/
-"""
+"""Support for the Yahoo! Weather service."""
import logging
from datetime import timedelta
@@ -17,8 +12,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['yahooweather==0.10']
-
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Weather details provided by Yahoo! Inc."
diff --git a/homeassistant/components/yweather/weather.py b/homeassistant/components/yweather/weather.py
index e4eb34a039a..4d7986d8a5c 100644
--- a/homeassistant/components/yweather/weather.py
+++ b/homeassistant/components/yweather/weather.py
@@ -10,8 +10,6 @@ from homeassistant.components.weather import (
from homeassistant.const import CONF_NAME, STATE_UNKNOWN, TEMP_CELSIUS
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ["yahooweather==0.10"]
-
_LOGGER = logging.getLogger(__name__)
DATA_CONDITION = 'yahoo_condition'
diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py
index f33c60b1c39..041f67b37ee 100644
--- a/homeassistant/components/zabbix/__init__.py
+++ b/homeassistant/components/zabbix/__init__.py
@@ -8,8 +8,6 @@ from homeassistant.const import (
CONF_PATH, CONF_HOST, CONF_SSL, CONF_PASSWORD, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['pyzabbix==0.7.4']
-
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSL = False
diff --git a/homeassistant/components/zabbix/manifest.json b/homeassistant/components/zabbix/manifest.json
new file mode 100644
index 00000000000..c0f100fa62f
--- /dev/null
+++ b/homeassistant/components/zabbix/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "zabbix",
+ "name": "Zabbix",
+ "documentation": "https://www.home-assistant.io/components/zabbix",
+ "requirements": [
+ "pyzabbix==0.7.4"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py
index ae2e70ede2c..004c176570a 100644
--- a/homeassistant/components/zabbix/sensor.py
+++ b/homeassistant/components/zabbix/sensor.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zabbix']
-
_CONF_TRIGGERS = 'triggers'
_CONF_HOSTIDS = 'hostids'
_CONF_INDIVIDUAL = 'individual'
diff --git a/homeassistant/components/zamg/manifest.json b/homeassistant/components/zamg/manifest.json
new file mode 100644
index 00000000000..ce16e1b523c
--- /dev/null
+++ b/homeassistant/components/zamg/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "zamg",
+ "name": "Zamg",
+ "documentation": "https://www.home-assistant.io/components/zamg",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py
index c101e4da920..9ce5da6fb95 100644
--- a/homeassistant/components/zamg/sensor.py
+++ b/homeassistant/components/zamg/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensor for data from Austrian "Zentralanstalt für Meteorologie und Geodynamik".
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.zamg/
-"""
+"""Sensor for the Austrian "Zentralanstalt für Meteorologie und Geodynamik"."""
import csv
from datetime import datetime, timedelta
import gzip
diff --git a/homeassistant/components/zengge/light.py b/homeassistant/components/zengge/light.py
index b283b8611dc..e066ad9da65 100644
--- a/homeassistant/components/zengge/light.py
+++ b/homeassistant/components/zengge/light.py
@@ -1,9 +1,4 @@
-"""
-Support for Zengge lights.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/light.zengge/
-"""
+"""Support for Zengge lights."""
import logging
import voluptuous as vol
@@ -15,8 +10,6 @@ from homeassistant.components.light import (
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
-REQUIREMENTS = ['zengge==0.2']
-
_LOGGER = logging.getLogger(__name__)
SUPPORT_ZENGGE_LED = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE)
@@ -157,6 +150,6 @@ class ZenggeLight(Light):
rgb = self._bulb.get_colour()
hsv = color_util.color_RGB_to_hsv(*rgb)
self._hs_color = hsv[:2]
- self._brightness = hsv[2]
+ self._brightness = (hsv[2] / 100) * 255
self._white = self._bulb.get_white()
self._state = self._bulb.get_on()
diff --git a/homeassistant/components/zengge/manifest.json b/homeassistant/components/zengge/manifest.json
new file mode 100644
index 00000000000..b846c95f5fa
--- /dev/null
+++ b/homeassistant/components/zengge/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "zengge",
+ "name": "Zengge",
+ "documentation": "https://www.home-assistant.io/components/zengge",
+ "requirements": [
+ "zengge==0.2"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py
index 844246528a6..d2dcd907885 100644
--- a/homeassistant/components/zeroconf/__init__.py
+++ b/homeassistant/components/zeroconf/__init__.py
@@ -7,11 +7,8 @@ import voluptuous as vol
from homeassistant import util
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, __version__)
-REQUIREMENTS = ['zeroconf==0.21.3']
-
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['api']
DOMAIN = 'zeroconf'
diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json
new file mode 100644
index 00000000000..bd7cf3ec0d6
--- /dev/null
+++ b/homeassistant/components/zeroconf/manifest.json
@@ -0,0 +1,14 @@
+{
+ "domain": "zeroconf",
+ "name": "Zeroconf",
+ "documentation": "https://www.home-assistant.io/components/zeroconf",
+ "requirements": [
+ "zeroconf==0.21.3"
+ ],
+ "dependencies": [
+ "api"
+ ],
+ "codeowners": [
+ "@robbiet480"
+ ]
+}
diff --git a/homeassistant/components/zestimate/manifest.json b/homeassistant/components/zestimate/manifest.json
new file mode 100644
index 00000000000..1d67ddbd581
--- /dev/null
+++ b/homeassistant/components/zestimate/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "zestimate",
+ "name": "Zestimate",
+ "documentation": "https://www.home-assistant.io/components/zestimate",
+ "requirements": [
+ "xmltodict==0.11.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py
index ed3af84d396..0a1f14324f6 100644
--- a/homeassistant/components/zestimate/sensor.py
+++ b/homeassistant/components/zestimate/sensor.py
@@ -1,9 +1,4 @@
-"""
-Support for zestimate data from zillow.com.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.zestimate/
-"""
+"""Support for zestimate data from zillow.com."""
from datetime import timedelta
import logging
@@ -17,8 +12,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
-REQUIREMENTS = ['xmltodict==0.11.0']
-
_LOGGER = logging.getLogger(__name__)
_RESOURCE = 'http://www.zillow.com/webservice/GetZestimate.htm'
@@ -120,12 +113,16 @@ class ZestimateDataSensor(Entity):
return
data = data_dict['response'][NAME]
details = {}
- details[ATTR_AMOUNT] = data['amount']['#text']
- details[ATTR_CURRENCY] = data['amount']['@currency']
- details[ATTR_LAST_UPDATED] = data['last-updated']
- details[ATTR_CHANGE] = int(data['valueChange']['#text'])
- details[ATTR_VAL_HI] = int(data['valuationRange']['high']['#text'])
- details[ATTR_VAL_LOW] = int(data['valuationRange']['low']['#text'])
+ if 'amount' in data and data['amount'] is not None:
+ details[ATTR_AMOUNT] = data['amount']['#text']
+ details[ATTR_CURRENCY] = data['amount']['@currency']
+ if 'last-updated' in data and data['last-updated'] is not None:
+ details[ATTR_LAST_UPDATED] = data['last-updated']
+ if 'valueChange' in data and data['valueChange'] is not None:
+ details[ATTR_CHANGE] = int(data['valueChange']['#text'])
+ if 'valuationRange' in data and data['valuationRange'] is not None:
+ details[ATTR_VAL_HI] = int(data['valuationRange']['high']['#text'])
+ details[ATTR_VAL_LOW] = int(data['valuationRange']['low']['#text'])
self.address = data_dict['response']['address']['street']
self.data = details
if self.data is not None:
diff --git a/homeassistant/components/zha/.translations/fr.json b/homeassistant/components/zha/.translations/fr.json
index de1a2274dd3..48328aed878 100644
--- a/homeassistant/components/zha/.translations/fr.json
+++ b/homeassistant/components/zha/.translations/fr.json
@@ -9,6 +9,7 @@
"step": {
"user": {
"data": {
+ "radio_type": "Type de radio",
"usb_path": "Chemin du p\u00e9riph\u00e9rique USB"
},
"title": "ZHA"
diff --git a/homeassistant/components/zha/.translations/ko.json b/homeassistant/components/zha/.translations/ko.json
index ffeaf4588e6..dfe4167cfcc 100644
--- a/homeassistant/components/zha/.translations/ko.json
+++ b/homeassistant/components/zha/.translations/ko.json
@@ -4,7 +4,7 @@
"single_instance_allowed": "\ud558\ub098\uc758 ZHA \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"error": {
- "cannot_connect": "ZHA \uc7a5\uce58\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4."
+ "cannot_connect": "ZHA \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4."
},
"step": {
"user": {
diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py
index 292b4fde61f..64760170150 100644
--- a/homeassistant/components/zha/__init__.py
+++ b/homeassistant/components/zha/__init__.py
@@ -1,9 +1,4 @@
-"""
-Support for Zigbee Home Automation devices.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/zha/
-"""
+"""Support for Zigbee Home Automation devices."""
import logging
import voluptuous as vol
@@ -16,23 +11,14 @@ from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
from . import config_flow # noqa # pylint: disable=unused-import
from . import api
from .core import ZHAGateway
+from .core.channels.registry import populate_channel_registry
from .core.const import (
COMPONENTS, CONF_BAUDRATE, CONF_DATABASE, CONF_DEVICE_CONFIG,
- CONF_RADIO_TYPE, CONF_USB_PATH, DATA_ZHA,
- DATA_ZHA_CONFIG, DATA_ZHA_CORE_COMPONENT, DATA_ZHA_DISPATCHERS,
- DATA_ZHA_RADIO, DEFAULT_BAUDRATE, DATA_ZHA_GATEWAY,
- DEFAULT_RADIO_TYPE, DOMAIN, RadioType, DATA_ZHA_CORE_EVENTS, ENABLE_QUIRKS)
-from .core.registries import establish_device_mappings
-from .core.channels.registry import populate_channel_registry
+ CONF_RADIO_TYPE, CONF_USB_PATH, DATA_ZHA, DATA_ZHA_CONFIG,
+ DATA_ZHA_CORE_COMPONENT, DATA_ZHA_DISPATCHERS, DATA_ZHA_GATEWAY,
+ DEFAULT_BAUDRATE, DEFAULT_RADIO_TYPE, DOMAIN, ENABLE_QUIRKS, RadioType)
from .core.patches import apply_cluster_listener_patch
-
-REQUIREMENTS = [
- 'bellows-homeassistant==0.7.2',
- 'zigpy-homeassistant==0.3.1',
- 'zigpy-xbee-homeassistant==0.1.3',
- 'zha-quirks==0.0.7',
- 'zigpy-deconz==0.1.3'
-]
+from .core.registries import establish_device_mappings
DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({
vol.Optional(ha_const.CONF_TYPE): cv.string,
@@ -143,9 +129,9 @@ async def async_setup_entry(hass, config_entry):
async def async_zha_shutdown(event):
"""Handle shutdown tasks."""
+ await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].shutdown()
await hass.data[DATA_ZHA][
DATA_ZHA_GATEWAY].async_update_device_storage()
- hass.data[DATA_ZHA][DATA_ZHA_RADIO].close()
hass.bus.async_listen_once(
ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown)
@@ -154,6 +140,8 @@ async def async_setup_entry(hass, config_entry):
async def async_unload_entry(hass, config_entry):
"""Unload ZHA config entry."""
+ await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].shutdown()
+
api.async_unload_api(hass)
dispatchers = hass.data[DATA_ZHA].get(DATA_ZHA_DISPATCHERS, [])
@@ -170,11 +158,5 @@ async def async_unload_entry(hass, config_entry):
for entity_id in entity_ids:
await component.async_remove_entity(entity_id)
- # clean up events
- hass.data[DATA_ZHA][DATA_ZHA_CORE_EVENTS].clear()
-
- _LOGGER.debug("Closing zha radio")
- hass.data[DATA_ZHA][DATA_ZHA_RADIO].close()
-
del hass.data[DATA_ZHA]
return True
diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py
index 2f88ad3a78b..0604c2fada4 100644
--- a/homeassistant/components/zha/api.py
+++ b/homeassistant/components/zha/api.py
@@ -1,9 +1,4 @@
-"""
-Web socket API for Zigbee Home Automation devices.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/zha/
-"""
+"""Web socket API for Zigbee Home Automation devices."""
import asyncio
import logging
@@ -356,10 +351,11 @@ async def websocket_get_bindable_devices(hass, connection, msg):
zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
source_ieee = msg[ATTR_IEEE]
source_device = zha_gateway.get_device(source_ieee)
+ ha_device_registry = await async_get_registry(hass)
devices = [
- {
- **device.device_info
- } for device in zha_gateway.devices.values() if
+ async_get_device_info(
+ hass, device, ha_device_registry=ha_device_registry
+ ) for device in zha_gateway.devices.values() if
async_is_bindable_target(source_device, device)
]
diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py
index 7c08c758af2..e9fa25c2577 100644
--- a/homeassistant/components/zha/binary_sensor.py
+++ b/homeassistant/components/zha/binary_sensor.py
@@ -1,9 +1,4 @@
-"""
-Binary sensors on Zigbee Home Automation networks.
-
-For more details on this platform, please refer to the documentation
-at https://home-assistant.io/components/binary_sensor.zha/
-"""
+"""Binary sensors on Zigbee Home Automation networks."""
import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
@@ -19,8 +14,6 @@ from .entity import ZhaEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zha']
-
# Zigbee Cluster Library Zone Type to Home Assistant device class
CLASS_MAPPING = {
0x000d: 'motion',
diff --git a/homeassistant/components/zha/const.py b/homeassistant/components/zha/const.py
index e7cf424990b..1ccc3e0ea25 100644
--- a/homeassistant/components/zha/const.py
+++ b/homeassistant/components/zha/const.py
@@ -1,9 +1,4 @@
-"""
-Backwards compatible constants bridge.
-
-For more details on this platform, please refer to the documentation
-at https://home-assistant.io/components/fan.zha/
-"""
+"""Backwards compatible constants bridge."""
# pylint: disable=W0614,W0401
from .core.const import * # noqa: F401,F403
from .core.registries import * # noqa: F401,F403
diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py
index 696a15b483b..0a96b4db4b8 100644
--- a/homeassistant/components/zha/core/channels/lighting.py
+++ b/homeassistant/components/zha/core/channels/lighting.py
@@ -29,10 +29,15 @@ class ColorChannel(ZigbeeChannel):
async def async_configure(self):
"""Configure channel."""
await self.fetch_color_capabilities(False)
+ await super().async_configure()
async def async_initialize(self, from_cache):
"""Initialize channel."""
await self.fetch_color_capabilities(True)
+ await self.get_attribute_value(
+ 'color_temperature', from_cache=from_cache)
+ await self.get_attribute_value('current_x', from_cache=from_cache)
+ await self.get_attribute_value('current_y', from_cache=from_cache)
async def fetch_color_capabilities(self, from_cache):
"""Get the color configuration."""
diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py
index a34a049fad2..193780c9124 100644
--- a/homeassistant/components/zha/core/const.py
+++ b/homeassistant/components/zha/core/const.py
@@ -17,7 +17,6 @@ BAUD_RATES = [
DATA_ZHA = 'zha'
DATA_ZHA_CONFIG = 'config'
DATA_ZHA_BRIDGE_ID = 'zha_bridge_id'
-DATA_ZHA_RADIO = 'zha_radio'
DATA_ZHA_DISPATCHERS = 'zha_dispatchers'
DATA_ZHA_CORE_COMPONENT = 'zha_core_component'
DATA_ZHA_CORE_EVENTS = 'zha_core_events'
diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py
index 71e41c2509b..17c7c6f878f 100644
--- a/homeassistant/components/zha/core/gateway.py
+++ b/homeassistant/components/zha/core/gateway.py
@@ -23,11 +23,11 @@ from .const import (
ADD_DEVICE_RELAY_LOGGERS, ATTR_MANUFACTURER, BELLOWS, CONF_BAUDRATE,
CONF_DATABASE, CONF_RADIO_TYPE, CONF_USB_PATH, CONTROLLER, CURRENT,
DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_CORE_COMPONENT, DATA_ZHA_GATEWAY,
- DATA_ZHA_RADIO, DEBUG_LEVELS, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME,
- DEVICE_FULL_INIT, DEVICE_INFO, DEVICE_JOINED, DEVICE_REMOVED, DOMAIN, IEEE,
- LOG_ENTRY, LOG_OUTPUT, MODEL, NWK, ORIGINAL, RADIO, RADIO_DESCRIPTION,
- RAW_INIT, SIGNAL_REMOVE, SIGNATURE, TYPE, ZHA, ZHA_GW_MSG, ZIGPY,
- ZIGPY_DECONZ, ZIGPY_XBEE)
+ DEBUG_LEVELS, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, DEVICE_FULL_INIT,
+ DEVICE_INFO, DEVICE_JOINED, DEVICE_REMOVED, DOMAIN, IEEE, LOG_ENTRY,
+ LOG_OUTPUT, MODEL, NWK, ORIGINAL, RADIO, RADIO_DESCRIPTION, RAW_INIT,
+ SIGNAL_REMOVE, SIGNATURE, TYPE, ZHA, ZHA_GW_MSG, ZIGPY, ZIGPY_DECONZ,
+ ZIGPY_XBEE)
from .device import DeviceStatus, ZHADevice
from .discovery import (
async_create_device_entity, async_dispatch_discovery_info,
@@ -76,7 +76,6 @@ class ZHAGateway:
radio = radio_details[RADIO]
self.radio_description = RADIO_TYPES[radio_type][RADIO_DESCRIPTION]
await radio.connect(usb_path, baudrate)
- self._hass.data[DATA_ZHA][DATA_ZHA_RADIO] = radio
if CONF_DATABASE in self._config:
database = self._config[CONF_DATABASE]
@@ -146,14 +145,14 @@ class ZHAGateway:
def device_removed(self, device):
"""Handle device being removed from the network."""
- device = self._devices.pop(device.ieee, None)
+ zha_device = self._devices.pop(device.ieee, None)
self._device_registry.pop(device.ieee, None)
- if device is not None:
- device_info = async_get_device_info(self._hass, device)
- self._hass.async_create_task(device.async_unsub_dispatcher())
+ if zha_device is not None:
+ device_info = async_get_device_info(self._hass, zha_device)
+ self._hass.async_create_task(zha_device.async_unsub_dispatcher())
async_dispatcher_send(
self._hass,
- "{}_{}".format(SIGNAL_REMOVE, str(device.ieee))
+ "{}_{}".format(SIGNAL_REMOVE, str(zha_device.ieee))
)
if device_info is not None:
async_dispatcher_send(
@@ -312,6 +311,11 @@ class ZHAGateway:
}
)
+ async def shutdown(self):
+ """Stop ZHA Controller Application."""
+ _LOGGER.debug("Shutting down ZHA ControllerApplication")
+ await self.application_controller.shutdown()
+
@callback
def async_capture_log_levels():
diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py
index 695f2be5960..ef7c2df6ce0 100644
--- a/homeassistant/components/zha/core/helpers.py
+++ b/homeassistant/components/zha/core/helpers.py
@@ -139,7 +139,7 @@ async def check_zigpy_connection(usb_path, radio_type, database_path):
await radio.connect(usb_path, DEFAULT_BAUDRATE)
controller = ControllerApplication(radio, database_path)
await asyncio.wait_for(controller.startup(auto_form=True), timeout=30)
- radio.close()
+ await controller.shutdown()
except Exception: # pylint: disable=broad-except
return False
return True
diff --git a/homeassistant/components/zha/device_entity.py b/homeassistant/components/zha/device_entity.py
index 7563481bbb7..3937e597b78 100644
--- a/homeassistant/components/zha/device_entity.py
+++ b/homeassistant/components/zha/device_entity.py
@@ -1,9 +1,4 @@
-"""
-Device entity for Zigbee Home Automation.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/zha/
-"""
+"""Device entity for Zigbee Home Automation."""
import logging
import time
diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py
index 1e98118e09f..d894ef5d7a3 100644
--- a/homeassistant/components/zha/entity.py
+++ b/homeassistant/components/zha/entity.py
@@ -1,9 +1,4 @@
-"""
-Entity for Zigbee Home Automation.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/zha/
-"""
+"""Entity for Zigbee Home Automation."""
import logging
import time
diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py
index 73989ef32b4..9619049bc7f 100644
--- a/homeassistant/components/zha/fan.py
+++ b/homeassistant/components/zha/fan.py
@@ -1,9 +1,4 @@
-"""
-Fans on Zigbee Home Automation networks.
-
-For more details on this platform, please refer to the documentation
-at https://home-assistant.io/components/fan.zha/
-"""
+"""Fans on Zigbee Home Automation networks."""
import logging
from homeassistant.core import callback
@@ -17,8 +12,6 @@ from .core.const import (
)
from .entity import ZhaEntity
-DEPENDENCIES = ['zha']
-
_LOGGER = logging.getLogger(__name__)
# Additional speeds in zigbee's ZCL
diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py
index fc29fd0cdd2..ec840d5edb3 100644
--- a/homeassistant/components/zha/light.py
+++ b/homeassistant/components/zha/light.py
@@ -1,9 +1,4 @@
-"""
-Lights on Zigbee Home Automation networks.
-
-For more details on this platform, please refer to the documentation
-at https://home-assistant.io/components/light.zha/
-"""
+"""Lights on Zigbee Home Automation networks."""
from datetime import timedelta
import logging
@@ -22,8 +17,6 @@ from .entity import ZhaEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zha']
-
DEFAULT_DURATION = 5
CAPABILITIES_COLOR_XY = 0x08
@@ -258,6 +251,22 @@ class Light(ZhaEntity, light.Light):
if self._level_channel:
self._brightness = await self._level_channel.get_attribute_value(
'current_level', from_cache=from_cache)
+ if self._color_channel:
+ color_capabilities = self._color_channel.get_color_capabilities()
+ if color_capabilities is not None and\
+ color_capabilities & CAPABILITIES_COLOR_TEMP:
+ self._color_temp = await\
+ self._color_channel.get_attribute_value(
+ 'color_temperature', from_cache=from_cache)
+ if color_capabilities is not None and\
+ color_capabilities & CAPABILITIES_COLOR_XY:
+ color_x = await self._color_channel.get_attribute_value(
+ 'current_x', from_cache=from_cache)
+ color_y = await self._color_channel.get_attribute_value(
+ 'current_y', from_cache=from_cache)
+ if color_x is not None and color_y is not None:
+ self._hs_color = color_util.color_xy_to_hs(
+ float(color_x / 65535), float(color_y / 65535))
async def refresh(self, time):
"""Call async_get_state at an interval."""
diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json
new file mode 100644
index 00000000000..c8bc0479f30
--- /dev/null
+++ b/homeassistant/components/zha/manifest.json
@@ -0,0 +1,17 @@
+{
+ "domain": "zha",
+ "name": "Zigbee Home Automation",
+ "documentation": "https://www.home-assistant.io/components/zha",
+ "requirements": [
+ "bellows-homeassistant==0.7.2",
+ "zha-quirks==0.0.8",
+ "zigpy-deconz==0.1.4",
+ "zigpy-homeassistant==0.3.2",
+ "zigpy-xbee-homeassistant==0.2.0"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@dmulcahey",
+ "@adminiuga"
+ ]
+}
diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py
index 56ce97c87a0..b6ac70fa187 100644
--- a/homeassistant/components/zha/sensor.py
+++ b/homeassistant/components/zha/sensor.py
@@ -1,9 +1,4 @@
-"""
-Sensors on Zigbee Home Automation networks.
-
-For more details on this platform, please refer to the documentation
-at https://home-assistant.io/components/sensor.zha/
-"""
+"""Sensors on Zigbee Home Automation networks."""
import logging
from homeassistant.core import callback
@@ -21,8 +16,6 @@ from .entity import ZhaEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zha']
-
# Formatter functions
def pass_through_formatter(value):
@@ -86,7 +79,7 @@ POLLING_REGISTRY = {
}
FORCE_UPDATE_REGISTRY = {
- ELECTRICAL_MEASUREMENT: True
+ ELECTRICAL_MEASUREMENT: False
}
diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py
index f1bf671a43d..7efcbabd74e 100644
--- a/homeassistant/components/zha/switch.py
+++ b/homeassistant/components/zha/switch.py
@@ -1,9 +1,4 @@
-"""
-Switches on Zigbee Home Automation networks.
-
-For more details on this platform, please refer to the documentation
-at https://home-assistant.io/components/switch.zha/
-"""
+"""Switches on Zigbee Home Automation networks."""
import logging
from homeassistant.components.switch import DOMAIN, SwitchDevice
@@ -18,8 +13,6 @@ from .entity import ZhaEntity
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zha']
-
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py
index 78cd7d16c48..d01d1028507 100644
--- a/homeassistant/components/zhong_hong/climate.py
+++ b/homeassistant/components/zhong_hong/climate.py
@@ -1,9 +1,4 @@
-"""
-Support for ZhongHong HVAC Controller.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/climate.zhong_hong/
-"""
+"""Support for ZhongHong HVAC Controller."""
import logging
import voluptuous as vol
@@ -19,8 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
async_dispatcher_send)
-REQUIREMENTS = ['zhong_hong_hvac==1.0.9']
-
_LOGGER = logging.getLogger(__name__)
CONF_GATEWAY_ADDRRESS = 'gateway_address'
diff --git a/homeassistant/components/zhong_hong/manifest.json b/homeassistant/components/zhong_hong/manifest.json
new file mode 100644
index 00000000000..6382a830dcf
--- /dev/null
+++ b/homeassistant/components/zhong_hong/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "zhong_hong",
+ "name": "Zhong hong",
+ "documentation": "https://www.home-assistant.io/components/zhong_hong",
+ "requirements": [
+ "zhong_hong_hvac==1.0.9"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/zigbee/__init__.py b/homeassistant/components/zigbee/__init__.py
index 0e2d3587829..516ac3453c8 100644
--- a/homeassistant/components/zigbee/__init__.py
+++ b/homeassistant/components/zigbee/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
-REQUIREMENTS = ['xbee-helper==0.0.7']
-
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'zigbee'
diff --git a/homeassistant/components/zigbee/binary_sensor.py b/homeassistant/components/zigbee/binary_sensor.py
index ccf4e70df34..8cf7f4d7dc0 100644
--- a/homeassistant/components/zigbee/binary_sensor.py
+++ b/homeassistant/components/zigbee/binary_sensor.py
@@ -8,8 +8,6 @@ from . import PLATFORM_SCHEMA, ZigBeeDigitalIn, ZigBeeDigitalInConfig
CONF_ON_STATE = 'on_state'
DEFAULT_ON_STATE = 'high'
-DEPENDENCIES = ['zigbee']
-
STATES = ['high', 'low']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/zigbee/light.py b/homeassistant/components/zigbee/light.py
index b9be0d89323..1ff38af02e4 100644
--- a/homeassistant/components/zigbee/light.py
+++ b/homeassistant/components/zigbee/light.py
@@ -8,8 +8,6 @@ from . import PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig
CONF_ON_STATE = 'on_state'
DEFAULT_ON_STATE = 'high'
-DEPENDENCIES = ['zigbee']
-
STATES = ['high', 'low']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/zigbee/manifest.json b/homeassistant/components/zigbee/manifest.json
new file mode 100644
index 00000000000..1e4076b8439
--- /dev/null
+++ b/homeassistant/components/zigbee/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "zigbee",
+ "name": "Zigbee",
+ "documentation": "https://www.home-assistant.io/components/zigbee",
+ "requirements": [
+ "xbee-helper==0.0.7"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/zigbee/sensor.py b/homeassistant/components/zigbee/sensor.py
index 48301ac9728..480064c5715 100644
--- a/homeassistant/components/zigbee/sensor.py
+++ b/homeassistant/components/zigbee/sensor.py
@@ -16,8 +16,6 @@ CONF_TYPE = 'type'
CONF_MAX_VOLTS = 'max_volts'
DEFAULT_VOLTS = 1.2
-DEPENDENCIES = ['zigbee']
-
TYPES = ['analog', 'temperature']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
diff --git a/homeassistant/components/zigbee/switch.py b/homeassistant/components/zigbee/switch.py
index ddfd47a047e..26a9e8fac83 100644
--- a/homeassistant/components/zigbee/switch.py
+++ b/homeassistant/components/zigbee/switch.py
@@ -5,12 +5,10 @@ from homeassistant.components.switch import SwitchDevice
from . import PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig
-DEPENDENCIES = ['zigbee']
CONF_ON_STATE = 'on_state'
DEFAULT_ON_STATE = 'high'
-DEPENDENCIES = ['zigbee']
STATES = ['high', 'low']
diff --git a/homeassistant/components/ziggo_mediabox_xl/manifest.json b/homeassistant/components/ziggo_mediabox_xl/manifest.json
new file mode 100644
index 00000000000..9e587137922
--- /dev/null
+++ b/homeassistant/components/ziggo_mediabox_xl/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "ziggo_mediabox_xl",
+ "name": "Ziggo mediabox xl",
+ "documentation": "https://www.home-assistant.io/components/ziggo_mediabox_xl",
+ "requirements": [
+ "ziggo-mediabox-xl==1.1.0"
+ ],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/homeassistant/components/ziggo_mediabox_xl/media_player.py b/homeassistant/components/ziggo_mediabox_xl/media_player.py
index abad22d89eb..9bbc6186924 100644
--- a/homeassistant/components/ziggo_mediabox_xl/media_player.py
+++ b/homeassistant/components/ziggo_mediabox_xl/media_player.py
@@ -1,9 +1,4 @@
-"""
-Support for interface with a Ziggo Mediabox XL.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/media_player.ziggo_mediabox_xl/
-"""
+"""Support for interface with a Ziggo Mediabox XL."""
import logging
import socket
@@ -19,8 +14,6 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['ziggo-mediabox-xl==1.1.0']
-
_LOGGER = logging.getLogger(__name__)
DATA_KNOWN_DEVICES = 'ziggo_mediabox_xl_known_devices'
diff --git a/homeassistant/components/zone/.translations/th.json b/homeassistant/components/zone/.translations/th.json
new file mode 100644
index 00000000000..e39765f2da2
--- /dev/null
+++ b/homeassistant/components/zone/.translations/th.json
@@ -0,0 +1,17 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "\u0e21\u0e35\u0e0a\u0e37\u0e48\u0e2d\u0e19\u0e35\u0e49\u0e2d\u0e22\u0e39\u0e48\u0e41\u0e25\u0e49\u0e27"
+ },
+ "step": {
+ "init": {
+ "data": {
+ "latitude": "\u0e40\u0e2a\u0e49\u0e19\u0e23\u0e38\u0e49\u0e07",
+ "longitude": "\u0e40\u0e2a\u0e49\u0e19\u0e41\u0e27\u0e07",
+ "name": "\u0e0a\u0e37\u0e48\u0e2d"
+ }
+ }
+ },
+ "title": "\u0e42\u0e0b\u0e19"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zone/manifest.json b/homeassistant/components/zone/manifest.json
new file mode 100644
index 00000000000..897908b61da
--- /dev/null
+++ b/homeassistant/components/zone/manifest.json
@@ -0,0 +1,10 @@
+{
+ "domain": "zone",
+ "name": "Zone",
+ "documentation": "https://www.home-assistant.io/components/zone",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/core"
+ ]
+}
diff --git a/homeassistant/components/zoneminder/__init__.py b/homeassistant/components/zoneminder/__init__.py
index a4d90d523aa..4e2585a34e3 100644
--- a/homeassistant/components/zoneminder/__init__.py
+++ b/homeassistant/components/zoneminder/__init__.py
@@ -11,8 +11,6 @@ from homeassistant.helpers.discovery import async_load_platform
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['zm-py==0.3.3']
-
CONF_PATH_ZMS = 'path_zms'
DEFAULT_PATH = '/zm/'
diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py
index ce59d4573be..23196cf571f 100644
--- a/homeassistant/components/zoneminder/binary_sensor.py
+++ b/homeassistant/components/zoneminder/binary_sensor.py
@@ -3,8 +3,6 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from . import DOMAIN as ZONEMINDER_DOMAIN
-DEPENDENCIES = ['zoneminder']
-
async def async_setup_platform(
hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py
index fe3333fa3ed..da625e6ee46 100644
--- a/homeassistant/components/zoneminder/camera.py
+++ b/homeassistant/components/zoneminder/camera.py
@@ -9,8 +9,6 @@ from . import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zoneminder']
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder cameras."""
diff --git a/homeassistant/components/zoneminder/manifest.json b/homeassistant/components/zoneminder/manifest.json
new file mode 100644
index 00000000000..9d371fbabf7
--- /dev/null
+++ b/homeassistant/components/zoneminder/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "zoneminder",
+ "name": "Zoneminder",
+ "documentation": "https://www.home-assistant.io/components/zoneminder",
+ "requirements": [
+ "zm-py==0.3.3"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@rohankapoorcom"
+ ]
+}
diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py
index e205d921422..6a44d335a3e 100644
--- a/homeassistant/components/zoneminder/sensor.py
+++ b/homeassistant/components/zoneminder/sensor.py
@@ -12,8 +12,6 @@ from . import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zoneminder']
-
CONF_INCLUDE_ARCHIVED = "include_archived"
DEFAULT_INCLUDE_ARCHIVED = False
diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py
index 78e72c5fd4a..d70ba8f8fd8 100644
--- a/homeassistant/components/zoneminder/switch.py
+++ b/homeassistant/components/zoneminder/switch.py
@@ -11,8 +11,6 @@ from . import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
-DEPENDENCIES = ['zoneminder']
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND_ON): cv.string,
vol.Required(CONF_COMMAND_OFF): cv.string,
diff --git a/homeassistant/components/zwave/.translations/nn.json b/homeassistant/components/zwave/.translations/nn.json
new file mode 100644
index 00000000000..ebd9d44796c
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/nn.json
@@ -0,0 +1,9 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "description": "Sj\u00e5 [www.home-assistant.io/docs/z-wave/installation/](https://www.home-assistant.io/docs/z-wave/installation/) for informasjon om konfigurasjonsvariablene."
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py
index 4abaaa31210..741c6f852a8 100644
--- a/homeassistant/components/zwave/__init__.py
+++ b/homeassistant/components/zwave/__init__.py
@@ -1,6 +1,7 @@
"""Support for Z-Wave."""
import asyncio
import copy
+from importlib import import_module
import logging
from pprint import pprint
@@ -8,7 +9,6 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback, CoreState
-from homeassistant.loader import get_platform
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent
@@ -37,8 +37,6 @@ from .discovery_schemas import DISCOVERY_SCHEMAS
from .util import (check_node_schema, check_value_schema, node_name,
check_has_unique_id, is_node_parsed)
-REQUIREMENTS = ['pydispatcher==2.0.5', 'homeassistant-pyozw==0.1.3']
-
_LOGGER = logging.getLogger(__name__)
CLASS_ID = 'class_id'
@@ -159,7 +157,8 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_AUTOHEAL, default=DEFAULT_CONF_AUTOHEAL): cv.boolean,
vol.Optional(CONF_CONFIG_PATH): cv.string,
- vol.Optional(CONF_NETWORK_KEY): cv.string,
+ vol.Optional(CONF_NETWORK_KEY):
+ vol.All(cv.string, vol.Match(r'(0x\w\w,\s?){15}0x\w\w')),
vol.Optional(CONF_DEVICE_CONFIG, default={}):
vol.Schema({cv.entity_id: DEVICE_CONFIG_SCHEMA_ENTRY}),
vol.Optional(CONF_DEVICE_CONFIG_GLOB, default={}):
@@ -523,10 +522,16 @@ async def async_setup_entry(hass, config_entry):
.values()):
if value.index != param:
continue
- if value.type in [const.TYPE_LIST, const.TYPE_BOOL]:
+ if value.type == const.TYPE_BOOL:
+ value.data = int(selection == 'True')
+ _LOGGER.info("Setting config parameter %s on Node %s "
+ "with bool selection %s", param, node_id,
+ str(selection))
+ return
+ if value.type == const.TYPE_LIST:
value.data = str(selection)
_LOGGER.info("Setting config parameter %s on Node %s "
- "with list/bool selection %s", param, node_id,
+ "with list selection %s", param, node_id,
str(selection))
return
if value.type == const.TYPE_BUTTON:
@@ -907,7 +912,9 @@ class ZWaveDeviceEntityValues():
if polling_intensity:
self.primary.enable_poll(polling_intensity)
- platform = get_platform(self._hass, component, DOMAIN)
+ platform = import_module('.{}'.format(component),
+ __name__)
+
device = platform.get_device(
node=self._node, values=self,
node_config=node_config, hass=self._hass)
diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py
index 34b5de18c8f..f33933a2772 100755
--- a/homeassistant/components/zwave/lock.py
+++ b/homeassistant/components/zwave/lock.py
@@ -31,9 +31,10 @@ WORKAROUND_ALARM_TYPE = 8
DEVICE_MAPPINGS = {
POLYCONTROL_DANALOCK_V2_BTZE_LOCK: WORKAROUND_V2BTZE,
- # Kwikset 914TRL ZW500
+ # Kwikset 914TRL ZW500 99100-078
(0x0090, 0x440): WORKAROUND_DEVICE_STATE,
(0x0090, 0x446): WORKAROUND_DEVICE_STATE,
+ (0x0090, 0x238): WORKAROUND_DEVICE_STATE,
# Yale Locks
# Yale YRD210, YRD220, YRL220
(0x0129, 0x0000): WORKAROUND_DEVICE_STATE | WORKAROUND_ALARM_TYPE,
diff --git a/homeassistant/components/zwave/manifest.json b/homeassistant/components/zwave/manifest.json
new file mode 100644
index 00000000000..598af58ad17
--- /dev/null
+++ b/homeassistant/components/zwave/manifest.json
@@ -0,0 +1,13 @@
+{
+ "domain": "zwave",
+ "name": "Z-Wave",
+ "documentation": "https://www.home-assistant.io/components/zwave",
+ "requirements": [
+ "homeassistant-pyozw==0.1.4",
+ "pydispatcher==2.0.5"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@home-assistant/z-wave"
+ ]
+}
diff --git a/homeassistant/components/zwave/services.yaml b/homeassistant/components/zwave/services.yaml
index 7c926a5a879..83e6ea2533b 100644
--- a/homeassistant/components/zwave/services.yaml
+++ b/homeassistant/components/zwave/services.yaml
@@ -30,15 +30,15 @@ heal_network:
description: Start a Z-Wave network heal. This might take a while and will slow down the Z-Wave network greatly while it is being processed. Refer to OZW_Log.txt for progress.
fields:
return_routes:
- description: Whether or not to update the return routes from the nodes to the controller. Defaults to False.
- example: True
+ description: Whether or not to update the return routes from the nodes to the controller. Defaults to False.
+ example: True
heal_node:
description: Start a Z-Wave node heal. Refer to OZW_Log.txt for progress.
fields:
return_routes:
- description: Whether or not to update the return routes from the node to the controller. Defaults to False.
- example: True
+ description: Whether or not to update the return routes from the node to the controller. Defaults to False.
+ example: True
remove_node:
description: Remove a node from the Z-Wave network. Refer to OZW_Log.txt for progress.
@@ -160,7 +160,7 @@ test_node:
example: 10
messages:
description: Optional. Amount of test messages to send.
- example: 3
+ example: 3
rename_node:
description: Set the name of a node. This will also affect the IDs of all entities in the node.
diff --git a/homeassistant/config.py b/homeassistant/config.py
index 19b8087e538..a7267441cdb 100644
--- a/homeassistant/config.py
+++ b/homeassistant/config.py
@@ -25,7 +25,9 @@ from homeassistant.const import (
CONF_TYPE, CONF_ID)
from homeassistant.core import callback, DOMAIN as CONF_CORE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
-from homeassistant.loader import get_component, get_platform
+from homeassistant.loader import (
+ Integration, async_get_integration, IntegrationNotFound
+)
from homeassistant.util.yaml import load_yaml, SECRET_YAML
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as date_util, location as loc_util
@@ -68,9 +70,6 @@ DEFAULT_CONFIG = """
# Configure a default setup of Home Assistant (frontend, api, etc)
default_config:
-# Show the introduction message on startup.
-introduction:
-
# Uncomment this if you are using SSL/TLS, running in Docker container, etc.
# http:
# base_url: example.duckdns.org:8123
@@ -85,7 +84,7 @@ sensor:
# Text to speech
tts:
- - platform: google
+ - platform: google_translate
group: !include groups.yaml
automation: !include automations.yaml
@@ -96,6 +95,15 @@ DEFAULT_SECRETS = """
# Learn more at https://home-assistant.io/docs/configuration/secrets/
some_password: welcome
"""
+TTS_PRE_92 = """
+tts:
+ - platform: google
+"""
+TTS_92 = """
+tts:
+ - platform: google_translate
+ service_name: google_say
+"""
def _no_duplicate_auth_provider(configs: Sequence[Dict[str, Any]]) \
@@ -311,11 +319,14 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> Dict:
raise HomeAssistantError(
"Config file not found in: {}".format(hass.config.config_dir))
config = load_yaml_config_file(path)
- core_config = config.get(CONF_CORE, {})
- merge_packages_config(hass, config, core_config.get(CONF_PACKAGES, {}))
return config
- return await hass.async_add_executor_job(_load_hass_yaml_config)
+ config = await hass.async_add_executor_job(_load_hass_yaml_config)
+ core_config = config.get(CONF_CORE, {})
+ await merge_packages_config(
+ hass, config, core_config.get(CONF_PACKAGES, {})
+ )
+ return config
def find_config_file(config_dir: Optional[str]) -> Optional[str]:
@@ -376,10 +387,24 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
+ if LooseVersion(conf_version) < LooseVersion('0.92'):
+ # 0.92 moved google/tts.py to google_translate/tts.py
+ config_path = find_config_file(hass.config.config_dir)
+ assert config_path is not None
+
+ with open(config_path, 'rt', encoding='utf-8') as config_file:
+ config_raw = config_file.read()
+
+ if TTS_PRE_92 in config_raw:
+ _LOGGER.info("Migrating google tts to google_translate tts")
+ config_raw = config_raw.replace(TTS_PRE_92, TTS_92)
+ with open(config_path, 'wt', encoding='utf-8') as config_file:
+ config_file.write(config_raw)
+
with open(version_path, 'wt') as outp:
outp.write(__version__)
- _LOGGER.info("Migrating old system configuration files to new locations")
+ _LOGGER.debug("Migrating old system configuration files to new locations")
for oldf, newf in FILE_MIGRATION:
if os.path.isfile(hass.config.path(oldf)):
_LOGGER.info("Migrating %s to %s", oldf, newf)
@@ -637,8 +662,10 @@ def _recursive_merge(
return error
-def merge_packages_config(hass: HomeAssistant, config: Dict, packages: Dict,
- _log_pkg_error: Callable = _log_pkg_error) -> Dict:
+async def merge_packages_config(hass: HomeAssistant, config: Dict,
+ packages: Dict,
+ _log_pkg_error: Callable = _log_pkg_error) \
+ -> Dict:
"""Merge packages into the top-level configuration. Mutate config."""
# pylint: disable=too-many-nested-blocks
PACKAGES_CONFIG_SCHEMA(packages)
@@ -649,12 +676,20 @@ def merge_packages_config(hass: HomeAssistant, config: Dict, packages: Dict,
# If component name is given with a trailing description, remove it
# when looking for component
domain = comp_name.split(' ')[0]
- component = get_component(hass, domain)
- if component is None:
+ try:
+ integration = await async_get_integration(hass, domain)
+ except IntegrationNotFound:
_log_pkg_error(pack_name, comp_name, config, "does not exist")
continue
+ try:
+ component = integration.get_component()
+ except ImportError:
+ _log_pkg_error(pack_name, comp_name, config,
+ "unable to import")
+ continue
+
if hasattr(component, 'PLATFORM_SCHEMA'):
if not comp_conf:
continue # Ensure we dont add Falsy items to list
@@ -704,72 +739,73 @@ def merge_packages_config(hass: HomeAssistant, config: Dict, packages: Dict,
return config
-@callback
-def async_process_component_config(
- hass: HomeAssistant, config: Dict, domain: str) -> Optional[Dict]:
+async def async_process_component_config(
+ hass: HomeAssistant, config: Dict, integration: Integration) \
+ -> Optional[Dict]:
"""Check component configuration and return processed configuration.
Returns None on error.
This method must be run in the event loop.
"""
- component = get_component(hass, domain)
+ domain = integration.domain
+ component = integration.get_component()
if hasattr(component, 'CONFIG_SCHEMA'):
try:
- config = component.CONFIG_SCHEMA(config) # type: ignore
+ return component.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
return None
- elif (hasattr(component, 'PLATFORM_SCHEMA') or
- hasattr(component, 'PLATFORM_SCHEMA_BASE')):
- platforms = []
- for p_name, p_config in config_per_platform(config, domain):
- # Validate component specific platform schema
- try:
- if hasattr(component, 'PLATFORM_SCHEMA_BASE'):
- p_validated = \
- component.PLATFORM_SCHEMA_BASE( # type: ignore
- p_config)
- else:
- p_validated = component.PLATFORM_SCHEMA( # type: ignore
- p_config)
- except vol.Invalid as ex:
- async_log_exception(ex, domain, p_config, hass)
- continue
+ component_platform_schema = getattr(
+ component, 'PLATFORM_SCHEMA_BASE',
+ getattr(component, 'PLATFORM_SCHEMA', None))
- # Not all platform components follow same pattern for platforms
- # So if p_name is None we are not going to validate platform
- # (the automation component is one of them)
- if p_name is None:
- platforms.append(p_validated)
- continue
+ if component_platform_schema is None:
+ return config
- platform = get_platform(hass, domain, p_name)
-
- if platform is None:
- continue
-
- # Validate platform specific schema
- if hasattr(platform, 'PLATFORM_SCHEMA'):
- # pylint: disable=no-member
- try:
- p_validated = platform.PLATFORM_SCHEMA( # type: ignore
- p_config)
- except vol.Invalid as ex:
- async_log_exception(ex, '{}.{}'.format(domain, p_name),
- p_config, hass)
- continue
+ platforms = []
+ for p_name, p_config in config_per_platform(config, domain):
+ # Validate component specific platform schema
+ try:
+ p_validated = component_platform_schema(p_config)
+ except vol.Invalid as ex:
+ async_log_exception(ex, domain, p_config, hass)
+ continue
+ # Not all platform components follow same pattern for platforms
+ # So if p_name is None we are not going to validate platform
+ # (the automation component is one of them)
+ if p_name is None:
platforms.append(p_validated)
+ continue
- # Create a copy of the configuration with all config for current
- # component removed and add validated config back in.
- filter_keys = extract_domain_configs(config, domain)
- config = {key: value for key, value in config.items()
- if key not in filter_keys}
- config[domain] = platforms
+ try:
+ p_integration = await async_get_integration(hass, p_name)
+ platform = p_integration.get_platform(domain)
+ except (IntegrationNotFound, ImportError):
+ continue
+
+ # Validate platform specific schema
+ if hasattr(platform, 'PLATFORM_SCHEMA'):
+ # pylint: disable=no-member
+ try:
+ p_validated = platform.PLATFORM_SCHEMA( # type: ignore
+ p_config)
+ except vol.Invalid as ex:
+ async_log_exception(ex, '{}.{}'.format(domain, p_name),
+ p_config, hass)
+ continue
+
+ platforms.append(p_validated)
+
+ # Create a copy of the configuration with all config for current
+ # component removed and add validated config back in.
+ filter_keys = extract_domain_configs(config, domain)
+ config = {key: value for key, value in config.items()
+ if key not in filter_keys}
+ config[domain] = platforms
return config
@@ -781,8 +817,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]:
"""
from homeassistant.scripts.check_config import check_ha_config_file
- res = await hass.async_add_executor_job(
- check_ha_config_file, hass)
+ res = await check_ha_config_file(hass) # type: ignore
if not res.errors:
return None
diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py
index df635807abe..393a046b5a2 100644
--- a/homeassistant/config_entries.py
+++ b/homeassistant/config_entries.py
@@ -123,10 +123,10 @@ import asyncio
import logging
import functools
import uuid
-from typing import Callable, Dict, List, Optional, Set # noqa pylint: disable=unused-import
+from typing import Callable, List, Optional, Set # noqa pylint: disable=unused-import
import weakref
-from homeassistant import data_entry_flow
+from homeassistant import data_entry_flow, loader
from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ConfigEntryNotReady
from homeassistant.setup import async_setup_component, async_process_deps_reqs
@@ -153,6 +153,7 @@ FLOWS = [
'geofency',
'gpslogger',
'hangouts',
+ 'heos',
'homematicip_cloud',
'hue',
'ifttt',
@@ -160,6 +161,7 @@ FLOWS = [
'ipma',
'lifx',
'locative',
+ 'logi_circle',
'luftdaten',
'mailgun',
'mobile_app',
@@ -288,23 +290,27 @@ class ConfigEntry:
self._async_cancel_retry_setup = None
async def async_setup(
- self, hass: HomeAssistant, *, component=None, tries=0) -> None:
+ self, hass: HomeAssistant, *,
+ integration: Optional[loader.Integration] = None, tries=0) -> None:
"""Set up an entry."""
- if component is None:
- component = getattr(hass.components, self.domain)
+ if integration is None:
+ integration = await loader.async_get_integration(hass, self.domain)
+
+ component = integration.get_component()
# Perform migration
- if component.DOMAIN == self.domain:
+ if integration.domain == self.domain:
if not await self.async_migrate(hass):
self.state = ENTRY_STATE_MIGRATION_ERROR
return
try:
- result = await component.async_setup_entry(hass, self)
+ result = await component.async_setup_entry( # type: ignore
+ hass, self)
if not isinstance(result, bool):
_LOGGER.error('%s.async_setup_entry did not return boolean',
- component.DOMAIN)
+ integration.domain)
result = False
except ConfigEntryNotReady:
self.state = ENTRY_STATE_SETUP_RETRY
@@ -317,18 +323,19 @@ class ConfigEntry:
async def setup_again(now):
"""Run setup again."""
self._async_cancel_retry_setup = None
- await self.async_setup(hass, component=component, tries=tries)
+ await self.async_setup(
+ hass, integration=integration, tries=tries)
self._async_cancel_retry_setup = \
hass.helpers.event.async_call_later(wait_time, setup_again)
return
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up entry %s for %s',
- self.title, component.DOMAIN)
+ self.title, integration.domain)
result = False
# Only store setup result as state if it was not forwarded.
- if self.domain != component.DOMAIN:
+ if self.domain != integration.domain:
return
if result:
@@ -336,15 +343,17 @@ class ConfigEntry:
else:
self.state = ENTRY_STATE_SETUP_ERROR
- async def async_unload(self, hass, *, component=None) -> bool:
+ async def async_unload(self, hass, *, integration=None) -> bool:
"""Unload an entry.
Returns if unload is possible and was successful.
"""
- if component is None:
- component = getattr(hass.components, self.domain)
+ if integration is None:
+ integration = await loader.async_get_integration(hass, self.domain)
- if component.DOMAIN == self.domain:
+ component = integration.get_component()
+
+ if integration.domain == self.domain:
if self.state in UNRECOVERABLE_STATES:
return False
@@ -359,7 +368,7 @@ class ConfigEntry:
supports_unload = hasattr(component, 'async_unload_entry')
if not supports_unload:
- if component.DOMAIN == self.domain:
+ if integration.domain == self.domain:
self.state = ENTRY_STATE_FAILED_UNLOAD
return False
@@ -369,27 +378,29 @@ class ConfigEntry:
assert isinstance(result, bool)
# Only adjust state if we unloaded the component
- if result and component.DOMAIN == self.domain:
+ if result and integration.domain == self.domain:
self.state = ENTRY_STATE_NOT_LOADED
return result
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error unloading entry %s for %s',
- self.title, component.DOMAIN)
- if component.DOMAIN == self.domain:
+ self.title, integration.domain)
+ if integration.domain == self.domain:
self.state = ENTRY_STATE_FAILED_UNLOAD
return False
async def async_remove(self, hass: HomeAssistant) -> None:
"""Invoke remove callback on component."""
- component = getattr(hass.components, self.domain)
+ integration = await loader.async_get_integration(hass, self.domain)
+ component = integration.get_component()
if not hasattr(component, 'async_remove_entry'):
return
try:
- await component.async_remove_entry(hass, self)
+ await component.async_remove_entry( # type: ignore
+ hass, self)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error calling entry remove callback %s for %s',
- self.title, component.DOMAIN)
+ self.title, integration.domain)
async def async_migrate(self, hass: HomeAssistant) -> bool:
"""Migrate an entry.
@@ -622,7 +633,7 @@ class ConfigEntries:
self._async_schedule_save()
- async def async_forward_entry_setup(self, entry, component):
+ async def async_forward_entry_setup(self, entry, domain):
"""Forward the setup of an entry to a different component.
By default an entry is setup with the component it belongs to. If that
@@ -633,24 +644,26 @@ class ConfigEntries:
setup of a component, because it can cause a deadlock.
"""
# Setup Component if not set up yet
- if component not in self.hass.config.components:
+ if domain not in self.hass.config.components:
result = await async_setup_component(
- self.hass, component, self._hass_config)
+ self.hass, domain, self._hass_config)
if not result:
return False
- await entry.async_setup(
- self.hass, component=getattr(self.hass.components, component))
+ integration = await loader.async_get_integration(self.hass, domain)
- async def async_forward_entry_unload(self, entry, component):
+ await entry.async_setup(self.hass, integration=integration)
+
+ async def async_forward_entry_unload(self, entry, domain):
"""Forward the unloading of an entry to a different component."""
# It was never loaded.
- if component not in self.hass.config.components:
+ if domain not in self.hass.config.components:
return True
- return await entry.async_unload(
- self.hass, component=getattr(self.hass.components, component))
+ integration = await loader.async_get_integration(self.hass, domain)
+
+ return await entry.async_unload(self.hass, integration=integration)
async def _async_finish_flow(self, flow, result):
"""Finish a config flow and add an entry."""
@@ -686,7 +699,25 @@ class ConfigEntries:
Handler key is the domain of the component that we want to set up.
"""
- component = getattr(self.hass.components, handler_key)
+ try:
+ integration = await loader.async_get_integration(
+ self.hass, handler_key)
+ except loader.IntegrationNotFound:
+ _LOGGER.error('Cannot find integration %s', handler_key)
+ raise data_entry_flow.UnknownHandler
+
+ # Make sure requirements and dependencies of component are resolved
+ await async_process_deps_reqs(
+ self.hass, self._hass_config, integration)
+
+ try:
+ integration.get_component()
+ except ImportError as err:
+ _LOGGER.error(
+ 'Error occurred while loading integration %s: %s',
+ handler_key, err)
+ raise data_entry_flow.UnknownHandler
+
handler = HANDLERS.get(handler_key)
if handler is None:
@@ -694,10 +725,6 @@ class ConfigEntries:
source = context['source']
- # Make sure requirements and dependencies of component are resolved
- await async_process_deps_reqs(
- self.hass, self._hass_config, handler, component)
-
# Create notification.
if source in DISCOVERY_SOURCES:
self.hass.bus.async_fire(EVENT_FLOW_DISCOVERED)
diff --git a/homeassistant/const.py b/homeassistant/const.py
index caaee0a8af1..9a376c04eea 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -1,8 +1,8 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
-MINOR_VERSION = 91
-PATCH_VERSION = '4'
+MINOR_VERSION = 92
+PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)
@@ -147,11 +147,6 @@ CONF_TTL = 'ttl'
CONF_TYPE = 'type'
CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement'
CONF_UNIT_SYSTEM = 'unit_system'
-
-# Deprecated in 0.88.0, invalidated in 0.91.0, remove in 0.92.0
-CONF_UPDATE_INTERVAL = 'update_interval'
-CONF_UPDATE_INTERVAL_INVALIDATION_VERSION = '0.91.0'
-
CONF_URL = 'url'
CONF_USERNAME = 'username'
CONF_VALUE_TEMPLATE = 'value_template'
@@ -185,9 +180,11 @@ EVENT_SCRIPT_STARTED = 'script_started'
DEVICE_CLASS_BATTERY = 'battery'
DEVICE_CLASS_HUMIDITY = 'humidity'
DEVICE_CLASS_ILLUMINANCE = 'illuminance'
+DEVICE_CLASS_SIGNAL_STRENGTH = 'signal_strength'
DEVICE_CLASS_TEMPERATURE = 'temperature'
DEVICE_CLASS_TIMESTAMP = 'timestamp'
DEVICE_CLASS_PRESSURE = 'pressure'
+DEVICE_CLASS_POWER = 'power'
# #### STATES ####
STATE_ON = 'on'
diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py
index 6b1dd10bd5b..f5b3e443d3a 100644
--- a/homeassistant/helpers/aiohttp_client.py
+++ b/homeassistant/helpers/aiohttp_client.py
@@ -168,7 +168,10 @@ def _async_get_connector(hass: HomeAssistantType,
else:
ssl_context = False
- connector = aiohttp.TCPConnector(loop=hass.loop, ssl=ssl_context)
+ connector = aiohttp.TCPConnector(loop=hass.loop,
+ enable_cleanup_closed=True,
+ ssl=ssl_context,
+ )
hass.data[key] = connector
async def _async_close_connector(event: Event) -> None:
diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py
index 8f5705bc67a..6d200a39c85 100644
--- a/homeassistant/helpers/config_entry_flow.py
+++ b/homeassistant/helpers/config_entry_flow.py
@@ -118,15 +118,35 @@ class WebhookFlowHandler(config_entries.ConfigFlow):
)
webhook_id = self.hass.components.webhook.async_generate_id()
- webhook_url = \
- self.hass.components.webhook.async_generate_url(webhook_id)
+
+ if self.hass.components.cloud.async_active_subscription():
+ webhook_url = \
+ await self.hass.components.cloud.async_create_cloudhook(
+ webhook_id
+ )
+ cloudhook = True
+ else:
+ webhook_url = \
+ self.hass.components.webhook.async_generate_url(webhook_id)
+ cloudhook = False
self._description_placeholder['webhook_url'] = webhook_url
return self.async_create_entry(
title=self._title,
data={
- 'webhook_id': webhook_id
+ 'webhook_id': webhook_id,
+ 'cloudhook': cloudhook,
},
description_placeholders=self._description_placeholder
)
+
+
+async def webhook_async_remove_entry(hass, entry) -> None:
+ """Remove a webhook config entry."""
+ if (not entry.data.get('cloudhook') or
+ 'cloud' not in hass.config.components):
+ return
+
+ await hass.components.cloud.async_delete_cloudhook(
+ entry.data['webhook_id'])
diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py
index 6513f9368b0..a954d01856e 100644
--- a/homeassistant/helpers/config_validation.py
+++ b/homeassistant/helpers/config_validation.py
@@ -349,6 +349,11 @@ def positive_timedelta(value: timedelta) -> timedelta:
return value
+def remove_falsy(value: Sequence[T]) -> Sequence[T]:
+ """Remove falsy values from a list."""
+ return [v for v in value if v]
+
+
def service(value):
"""Validate service."""
# Services use same format as entities so we can use same helper.
diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py
index 34f9a95b3a4..0c547166d1a 100644
--- a/homeassistant/helpers/discovery.py
+++ b/homeassistant/helpers/discovery.py
@@ -50,15 +50,15 @@ def async_listen(hass, service, callback):
@bind_hass
-def discover(hass, service, discovered=None, component=None, hass_config=None):
+def discover(hass, service, discovered, component, hass_config):
"""Fire discovery event. Can ensure a component is loaded."""
hass.add_job(
async_discover(hass, service, discovered, component, hass_config))
@bind_hass
-async def async_discover(hass, service, discovered=None, component=None,
- hass_config=None):
+async def async_discover(hass, service, discovered, component,
+ hass_config):
"""Fire discovery event. Can ensure a component is loaded."""
if component in DEPENDENCY_BLACKLIST:
raise HomeAssistantError(
diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py
index 4ef5513baf7..d69cdd3d997 100644
--- a/homeassistant/helpers/entity.py
+++ b/homeassistant/helpers/entity.py
@@ -241,9 +241,9 @@ class Entity:
"""Write the state to the state machine."""
start = timer()
+ attr = {}
if not self.available:
state = STATE_UNAVAILABLE
- attr = {}
else:
state = self.state
@@ -252,10 +252,8 @@ class Entity:
else:
state = str(state)
- attr = self.state_attributes or {}
- device_attr = self.device_state_attributes
- if device_attr is not None:
- attr.update(device_attr)
+ attr.update(self.state_attributes or {})
+ attr.update(self.device_state_attributes or {})
unit_of_measurement = self.unit_of_measurement
if unit_of_measurement is not None:
diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py
index 744cf36ea66..5a5f3dc8177 100644
--- a/homeassistant/helpers/entity_component.py
+++ b/homeassistant/helpers/entity_component.py
@@ -13,7 +13,7 @@ from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.service import async_extract_entity_ids
-from homeassistant.loader import bind_hass
+from homeassistant.loader import bind_hass, async_get_integration
from homeassistant.util import slugify
from .entity_platform import EntityPlatform
@@ -124,7 +124,11 @@ class EntityComponent:
"""Set up a config entry."""
platform_type = config_entry.domain
platform = await async_prepare_setup_platform(
- self.hass, self.config, self.domain, platform_type)
+ self.hass,
+ # In future PR we should make hass_config part of the constructor
+ # params.
+ self.config or {},
+ self.domain, platform_type)
if platform is None:
return False
@@ -179,13 +183,15 @@ class EntityComponent:
if entity.available and entity.entity_id in entity_ids]
@callback
- def async_register_entity_service(self, name, schema, func):
+ def async_register_entity_service(self, name, schema, func,
+ required_features=None):
"""Register an entity service."""
async def handle_service(call):
"""Handle the service."""
service_name = "{}.{}".format(self.domain, name)
await self.hass.helpers.service.entity_service_call(
- self._platforms.values(), func, call, service_name
+ self._platforms.values(), func, call, service_name,
+ required_features
)
self.hass.services.async_register(
@@ -274,8 +280,10 @@ class EntityComponent:
self.logger.error(err)
return None
- conf = conf_util.async_process_component_config(
- self.hass, conf, self.domain)
+ integration = await async_get_integration(self.hass, self.domain)
+
+ conf = await conf_util.async_process_component_config(
+ self.hass, conf, integration)
if conf is None:
return None
diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py
index 43b8318abc5..8c576f58c14 100644
--- a/homeassistant/helpers/service.py
+++ b/homeassistant/helpers/service.py
@@ -2,21 +2,22 @@
import asyncio
from functools import wraps
import logging
-from os import path
from typing import Callable
import voluptuous as vol
-from homeassistant.auth.permissions.const import POLICY_CONTROL
+from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL
from homeassistant.const import (
ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ATTR_AREA_ID)
import homeassistant.core as ha
-from homeassistant.exceptions import TemplateError, Unauthorized, UnknownUser
+from homeassistant.exceptions import (
+ HomeAssistantError, TemplateError, Unauthorized, UnknownUser)
from homeassistant.helpers import template, typing
-from homeassistant.loader import get_component, bind_hass
+from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.util.yaml import load_yaml
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async_ import run_coroutine_threadsafe
+from homeassistant.helpers.typing import HomeAssistantType
CONF_SERVICE = 'service'
CONF_SERVICE_TEMPLATE = 'service_template'
@@ -152,68 +153,67 @@ async def async_extract_entity_ids(hass, service_call, expand_group=True):
return extracted
+async def _load_services_file(hass: HomeAssistantType, domain: str):
+ """Load services file for an integration."""
+ integration = await async_get_integration(hass, domain)
+ try:
+ return await hass.async_add_executor_job(
+ load_yaml, str(integration.file_path / 'services.yaml'))
+ except FileNotFoundError:
+ _LOGGER.warning("Unable to find services.yaml for the %s integration",
+ domain)
+ return {}
+ except HomeAssistantError:
+ _LOGGER.warning("Unable to parse services.yaml for the %s integration",
+ domain)
+ return {}
+
+
@bind_hass
async def async_get_all_descriptions(hass):
"""Return descriptions (i.e. user documentation) for all service calls."""
- if SERVICE_DESCRIPTION_CACHE not in hass.data:
- hass.data[SERVICE_DESCRIPTION_CACHE] = {}
- description_cache = hass.data[SERVICE_DESCRIPTION_CACHE]
-
+ descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {})
format_cache_key = '{}.{}'.format
-
- def domain_yaml_file(domain):
- """Return the services.yaml location for a domain."""
- if domain == ha.DOMAIN:
- from homeassistant import components
- component_path = path.dirname(components.__file__)
- else:
- component_path = path.dirname(get_component(hass, domain).__file__)
- return path.join(component_path, 'services.yaml')
-
- def load_services_files(yaml_files):
- """Load and parse services.yaml files."""
- loaded = {}
- for yaml_file in yaml_files:
- try:
- loaded[yaml_file] = load_yaml(yaml_file)
- except FileNotFoundError:
- loaded[yaml_file] = {}
-
- return loaded
-
services = hass.services.async_services()
- # Load missing files
+ # See if there are new services not seen before.
+ # Any service that we saw before already has an entry in description_cache.
missing = set()
for domain in services:
for service in services[domain]:
- if format_cache_key(domain, service) not in description_cache:
- missing.add(domain_yaml_file(domain))
+ if format_cache_key(domain, service) not in descriptions_cache:
+ missing.add(domain)
break
+ # Files we loaded for missing descriptions
+ loaded = {}
+
if missing:
- loaded = await hass.async_add_job(load_services_files, missing)
+ contents = await asyncio.gather(*[
+ _load_services_file(hass, domain) for domain in missing
+ ])
+
+ for domain, content in zip(missing, contents):
+ loaded[domain] = content
# Build response
- catch_all_yaml_file = domain_yaml_file(ha.DOMAIN)
descriptions = {}
for domain in services:
descriptions[domain] = {}
- yaml_file = domain_yaml_file(domain)
for service in services[domain]:
cache_key = format_cache_key(domain, service)
- description = description_cache.get(cache_key)
+ description = descriptions_cache.get(cache_key)
# Cache missing descriptions
if description is None:
- if yaml_file == catch_all_yaml_file:
- yaml_services = loaded[yaml_file].get(domain, {})
- else:
- yaml_services = loaded[yaml_file]
- yaml_description = yaml_services.get(service, {})
+ domain_yaml = loaded[domain]
+ yaml_description = domain_yaml.get(service, {})
- description = description_cache[cache_key] = {
+ # Don't warn for missing services, because it triggers false
+ # positives for things like scripts, that register as a service
+
+ description = descriptions_cache[cache_key] = {
'description': yaml_description.get('description', ''),
'fields': yaml_description.get('fields', {})
}
@@ -224,7 +224,8 @@ async def async_get_all_descriptions(hass):
@bind_hass
-async def entity_service_call(hass, platforms, func, call, service_name=''):
+async def entity_service_call(hass, platforms, func, call, service_name='',
+ required_features=None):
"""Handle an entity service call.
Calls all platforms simultaneously.
@@ -303,7 +304,8 @@ async def entity_service_call(hass, platforms, func, call, service_name=''):
platforms_entities.append(platform_entities)
tasks = [
- _handle_service_platform_call(func, data, entities, call.context)
+ _handle_service_platform_call(func, data, entities, call.context,
+ required_features)
for platform, entities in zip(platforms, platforms_entities)
]
@@ -314,7 +316,8 @@ async def entity_service_call(hass, platforms, func, call, service_name=''):
future.result() # pop exception if have
-async def _handle_service_platform_call(func, data, entities, context):
+async def _handle_service_platform_call(func, data, entities, context,
+ required_features):
"""Handle a function call."""
tasks = []
@@ -322,6 +325,11 @@ async def _handle_service_platform_call(func, data, entities, context):
if not entity.available:
continue
+ # Skip entities that don't have the required feature.
+ if required_features is not None \
+ and not entity.supported_features & required_features:
+ continue
+
entity.async_set_context(context)
if isinstance(func, str):
@@ -341,9 +349,10 @@ async def _handle_service_platform_call(func, data, entities, context):
@bind_hass
@ha.callback
-def async_register_admin_service(hass: typing.HomeAssistantType, domain: str,
- service: str, service_func: Callable,
- schema: vol.Schema) -> None:
+def async_register_admin_service(
+ hass: typing.HomeAssistantType, domain: str,
+ service: str, service_func: Callable,
+ schema: vol.Schema = vol.Schema({}, extra=vol.PREVENT_EXTRA)) -> None:
"""Register a service that requires admin access."""
@wraps(service_func)
async def admin_handler(call):
@@ -359,3 +368,47 @@ def async_register_admin_service(hass: typing.HomeAssistantType, domain: str,
hass.services.async_register(
domain, service, admin_handler, schema
)
+
+
+@bind_hass
+@ha.callback
+def verify_domain_control(hass: HomeAssistantType, domain: str) -> Callable:
+ """Ensure permission to access any entity under domain in service call."""
+ def decorator(service_handler: Callable) -> Callable:
+ """Decorate."""
+ if not asyncio.iscoroutinefunction(service_handler):
+ raise HomeAssistantError(
+ 'Can only decorate async functions.')
+
+ async def check_permissions(call):
+ """Check user permission and raise before call if unauthorized."""
+ if not call.context.user_id:
+ return await service_handler(call)
+
+ user = await hass.auth.async_get_user(call.context.user_id)
+ if user is None:
+ raise UnknownUser(
+ context=call.context,
+ permission=POLICY_CONTROL,
+ user_id=call.context.user_id)
+
+ reg = await hass.helpers.entity_registry.async_get_registry()
+ entities = [
+ entity.entity_id for entity in reg.entities.values()
+ if entity.platform == domain
+ ]
+
+ for entity_id in entities:
+ if user.permissions.check_entity(entity_id, POLICY_CONTROL):
+ return await service_handler(call)
+
+ raise Unauthorized(
+ context=call.context,
+ permission=POLICY_CONTROL,
+ user_id=call.context.user_id,
+ perm_category=CAT_ENTITIES
+ )
+
+ return check_permissions
+
+ return decorator
diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py
index 63a6421d5f6..4f655e692f7 100644
--- a/homeassistant/helpers/translation.py
+++ b/homeassistant/helpers/translation.py
@@ -1,10 +1,9 @@
"""Translation string lookup helpers."""
import logging
-import pathlib
-from typing import Any, Dict, Iterable
+from typing import Any, Dict, Iterable, Optional
from homeassistant import config_entries
-from homeassistant.loader import get_component, get_platform, bind_hass
+from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.util.json import load_json
from .typing import HomeAssistantType
@@ -30,53 +29,36 @@ def flatten(data: Dict) -> Dict[str, Any]:
return recursive_flatten('', data)
-def component_translation_file(hass: HomeAssistantType, component: str,
- language: str) -> str:
+async def component_translation_file(hass: HomeAssistantType, component: str,
+ language: str) -> Optional[str]:
"""Return the translation json file location for a component.
- For component one of:
- - components/light/.translations/nl.json
- - components/.translations/group.nl.json
+ For component:
+ - components/hue/.translations/nl.json
- For platform one of:
- - components/light/.translations/hue.nl.json
+ For platform:
- components/hue/.translations/light.nl.json
+
+ If component is just a single file, will return None.
"""
- is_platform = '.' in component
+ parts = component.split('.')
+ domain = parts[-1]
+ is_platform = len(parts) == 2
- if not is_platform:
- module = get_component(hass, component)
- assert module is not None
+ integration = await async_get_integration(hass, domain)
+ assert integration is not None, domain
- module_path = pathlib.Path(module.__file__)
-
- if module.__name__ == module.__package__:
- # light/__init__.py
- filename = '{}.json'.format(language)
- else:
- # group.py
- filename = '{}.{}.json'.format(component, language)
-
- return str(module_path.parent / '.translations' / filename)
-
- # It's a platform
- parts = component.split('.', 1)
- module = get_platform(hass, *parts)
- assert module is not None, component
-
- # Either within HA or custom_components
- # Either light/hue.py or hue/light.py
- module_path = pathlib.Path(module.__file__)
-
- # Compare to parent so we don't have to strip off `.py`
- if module_path.parent.name == parts[0]:
- # this is light/hue.py
- filename = "{}.{}.json".format(parts[1], language)
- else:
- # this is hue/light.py
+ if is_platform:
filename = "{}.{}.json".format(parts[0], language)
+ return str(integration.file_path / '.translations' / filename)
- return str(module_path.parent / '.translations' / filename)
+ # If it's a component that is just one file, we don't support translations
+ # Example custom_components/my_component.py
+ if integration.file_path.name != domain:
+ return None
+
+ filename = '{}.json'.format(language)
+ return str(integration.file_path / '.translations' / filename)
def load_translations_files(translation_files: Dict[str, str]) \
@@ -130,8 +112,12 @@ async def async_get_component_resources(hass: HomeAssistantType,
missing_components = components - set(translation_cache)
missing_files = {}
for component in missing_components:
- missing_files[component] = component_translation_file(
- hass, component, language)
+ path = await component_translation_file(hass, component, language)
+ # No translation available
+ if path is None:
+ translation_cache[component] = {}
+ else:
+ missing_files[component] = path
# Load missing files
if missing_files:
diff --git a/homeassistant/loader.py b/homeassistant/loader.py
index 4ca19935206..fb2c1bae894 100644
--- a/homeassistant/loader.py
+++ b/homeassistant/loader.py
@@ -1,50 +1,223 @@
"""
-The methods for loading Home Assistant components.
+The methods for loading Home Assistant integrations.
This module has quite some complex parts. I have tried to add as much
documentation as possible to keep it understandable.
-
-Components can be accessed via hass.components.switch from your code.
-If you want to retrieve a platform that is part of a component, you should
-call get_component(hass, 'switch.your_platform'). In both cases the config
-directory is checked to see if it contains a user provided version. If not
-available it will check the built-in components and platforms.
"""
+import asyncio
import functools as ft
import importlib
+import json
import logging
+import pathlib
import sys
from types import ModuleType
-from typing import Optional, Set, TYPE_CHECKING, Callable, Any, TypeVar, List # noqa pylint: disable=unused-import
-
-from homeassistant.const import PLATFORM_FORMAT
+from typing import (
+ Optional,
+ Set,
+ TYPE_CHECKING,
+ Callable,
+ Any,
+ TypeVar,
+ List,
+ Dict,
+ Union,
+ cast,
+)
# Typing imports that create a circular dependency
# pylint: disable=using-constant-test,unused-import
if TYPE_CHECKING:
- from homeassistant.core import HomeAssistant # NOQA
+ from homeassistant.core import HomeAssistant # noqa
CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) # noqa pylint: disable=invalid-name
-PREPARED = False
-
DEPENDENCY_BLACKLIST = {'config'}
_LOGGER = logging.getLogger(__name__)
-DATA_KEY = 'components'
+DATA_COMPONENTS = 'components'
+DATA_INTEGRATIONS = 'integrations'
PACKAGE_CUSTOM_COMPONENTS = 'custom_components'
PACKAGE_BUILTIN = 'homeassistant.components'
LOOKUP_PATHS = [PACKAGE_CUSTOM_COMPONENTS, PACKAGE_BUILTIN]
-COMPONENTS_WITH_BAD_PLATFORMS = ['automation', 'mqtt', 'telegram_bot']
+CUSTOM_WARNING = (
+ 'You are using a custom integration for %s which has not '
+ 'been tested by Home Assistant. This component might '
+ 'cause stability problems, be sure to disable it if you '
+ 'do experience issues with Home Assistant.'
+)
+_UNDEF = object()
+
+
+def manifest_from_legacy_module(domain: str, module: ModuleType) -> Dict:
+ """Generate a manifest from a legacy module."""
+ return {
+ 'domain': domain,
+ 'name': domain,
+ 'documentation': None,
+ 'requirements': getattr(module, 'REQUIREMENTS', []),
+ 'dependencies': getattr(module, 'DEPENDENCIES', []),
+ 'codeowners': [],
+ }
+
+
+class Integration:
+ """An integration in Home Assistant."""
+
+ @classmethod
+ def resolve_from_root(cls, hass: 'HomeAssistant', root_module: ModuleType,
+ domain: str) -> 'Optional[Integration]':
+ """Resolve an integration from a root module."""
+ for base in root_module.__path__: # type: ignore
+ manifest_path = (
+ pathlib.Path(base) / domain / 'manifest.json'
+ )
+
+ if not manifest_path.is_file():
+ continue
+
+ try:
+ manifest = json.loads(manifest_path.read_text())
+ except ValueError as err:
+ _LOGGER.error("Error parsing manifest.json file at %s: %s",
+ manifest_path, err)
+ continue
+
+ return cls(
+ hass, "{}.{}".format(root_module.__name__, domain),
+ manifest_path.parent, manifest
+ )
+
+ return None
+
+ @classmethod
+ def resolve_legacy(cls, hass: 'HomeAssistant', domain: str) \
+ -> 'Optional[Integration]':
+ """Resolve legacy component.
+
+ Will create a stub manifest.
+ """
+ comp = _load_file(hass, domain, LOOKUP_PATHS)
+
+ if comp is None:
+ return None
+
+ return cls(
+ hass, comp.__name__, pathlib.Path(comp.__file__).parent,
+ manifest_from_legacy_module(domain, comp)
+ )
+
+ def __init__(self, hass: 'HomeAssistant', pkg_path: str,
+ file_path: pathlib.Path, manifest: Dict):
+ """Initialize an integration."""
+ self.hass = hass
+ self.pkg_path = pkg_path
+ self.file_path = file_path
+ self.name = manifest['name'] # type: str
+ self.domain = manifest['domain'] # type: str
+ self.dependencies = manifest['dependencies'] # type: List[str]
+ self.after_dependencies = manifest.get(
+ 'after_dependencies') # type: Optional[List[str]]
+ self.requirements = manifest['requirements'] # type: List[str]
+ _LOGGER.info("Loaded %s from %s", self.domain, pkg_path)
+
+ def get_component(self) -> ModuleType:
+ """Return the component."""
+ cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
+ if self.domain not in cache:
+ cache[self.domain] = importlib.import_module(self.pkg_path)
+ return cache[self.domain] # type: ignore
+
+ def get_platform(self, platform_name: str) -> ModuleType:
+ """Return a platform for an integration."""
+ cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
+ full_name = "{}.{}".format(self.domain, platform_name)
+ if full_name not in cache:
+ cache[full_name] = importlib.import_module(
+ "{}.{}".format(self.pkg_path, platform_name)
+ )
+ return cache[full_name] # type: ignore
+
+ def __repr__(self) -> str:
+ """Text representation of class."""
+ return "".format(self.domain, self.pkg_path)
+
+
+async def async_get_integration(hass: 'HomeAssistant', domain: str)\
+ -> Integration:
+ """Get an integration."""
+ cache = hass.data.get(DATA_INTEGRATIONS)
+ if cache is None:
+ if not _async_mount_config_dir(hass):
+ raise IntegrationNotFound(domain)
+ cache = hass.data[DATA_INTEGRATIONS] = {}
+
+ int_or_evt = cache.get(
+ domain, _UNDEF) # type: Optional[Union[Integration, asyncio.Event]]
+
+ if isinstance(int_or_evt, asyncio.Event):
+ await int_or_evt.wait()
+ int_or_evt = cache.get(domain, _UNDEF)
+
+ # When we have waited and it's _UNDEF, it doesn't exist
+ # We don't cache that it doesn't exist, or else people can't fix it
+ # and then restart, because their config will never be valid.
+ if int_or_evt is _UNDEF:
+ raise IntegrationNotFound(domain)
+
+ if int_or_evt is not _UNDEF:
+ return cast(Integration, int_or_evt)
+
+ event = cache[domain] = asyncio.Event()
+
+ try:
+ import custom_components
+ integration = await hass.async_add_executor_job(
+ Integration.resolve_from_root, hass, custom_components, domain
+ )
+ if integration is not None:
+ _LOGGER.warning(CUSTOM_WARNING, domain)
+ cache[domain] = integration
+ event.set()
+ return integration
+
+ except ImportError:
+ # Import error if "custom_components" doesn't exist
+ pass
+
+ from homeassistant import components
+
+ integration = await hass.async_add_executor_job(
+ Integration.resolve_from_root, hass, components, domain
+ )
+
+ if integration is not None:
+ cache[domain] = integration
+ event.set()
+ return integration
+
+ integration = Integration.resolve_legacy(hass, domain)
+ if integration is not None:
+ cache[domain] = integration
+ else:
+ # Remove event from cache.
+ cache.pop(domain)
+
+ event.set()
+
+ if not integration:
+ raise IntegrationNotFound(domain)
+
+ return integration
class LoaderError(Exception):
"""Loader base error."""
-class ComponentNotFound(LoaderError):
+class IntegrationNotFound(LoaderError):
"""Raised when a component is not found."""
def __init__(self, domain: str) -> None:
@@ -64,95 +237,6 @@ class CircularDependency(LoaderError):
self.to_domain = to_domain
-def set_component(hass, # type: HomeAssistant
- comp_name: str, component: Optional[ModuleType]) -> None:
- """Set a component in the cache.
-
- Async friendly.
- """
- cache = hass.data.setdefault(DATA_KEY, {})
- cache[comp_name] = component
-
-
-def get_platform(hass, # type: HomeAssistant
- domain: str, platform_name: str) -> Optional[ModuleType]:
- """Try to load specified platform.
-
- Example invocation: get_platform(hass, 'light', 'hue')
-
- Async friendly.
- """
- # If the platform has a component, we will limit the platform loading path
- # to be the same source (custom/built-in).
- if domain not in COMPONENTS_WITH_BAD_PLATFORMS:
- component = _load_file(hass, platform_name, LOOKUP_PATHS)
- else:
- # avoid load component for legacy platform
- component = None
-
- # Until we have moved all platforms under their component/own folder, it
- # can be that the component is None.
- if component is not None:
- base_paths = [component.__name__.rsplit('.', 1)[0]]
- else:
- base_paths = LOOKUP_PATHS
-
- platform = _load_file(
- hass, PLATFORM_FORMAT.format(domain=domain, platform=platform_name),
- base_paths)
-
- if platform is not None:
- return platform
-
- # Legacy platform check for automation: components/automation/event.py
- if component is None and domain in COMPONENTS_WITH_BAD_PLATFORMS:
- platform = _load_file(
- hass,
- PLATFORM_FORMAT.format(domain=platform_name, platform=domain),
- base_paths
- )
-
- # Legacy platform check for custom: custom_components/light/hue.py
- # Only check if the component was also in custom components.
- if component is None or base_paths[0] == PACKAGE_CUSTOM_COMPONENTS:
- platform = _load_file(
- hass,
- PLATFORM_FORMAT.format(domain=platform_name, platform=domain),
- [PACKAGE_CUSTOM_COMPONENTS]
- )
-
- if platform is None:
- if component is None:
- extra = ""
- else:
- extra = " Search path was limited to path of component: {}".format(
- base_paths[0])
- _LOGGER.error("Unable to find platform %s.%s", platform_name, extra)
- return None
-
- if domain not in COMPONENTS_WITH_BAD_PLATFORMS:
- _LOGGER.error(
- "Integrations need to be in their own folder. Change %s/%s.py to "
- "%s/%s.py. This will stop working soon.",
- domain, platform_name, platform_name, domain)
-
- return platform
-
-
-def get_component(hass, # type: HomeAssistant
- comp_or_platform: str) -> Optional[ModuleType]:
- """Try to load specified component.
-
- Async friendly.
- """
- comp = _load_file(hass, comp_or_platform, LOOKUP_PATHS)
-
- if comp is None:
- _LOGGER.error("Unable to find component %s", comp_or_platform)
-
- return comp
-
-
def _load_file(hass, # type: HomeAssistant
comp_or_platform: str,
base_paths: List[str]) -> Optional[ModuleType]:
@@ -163,19 +247,15 @@ def _load_file(hass, # type: HomeAssistant
Async friendly.
"""
try:
- return hass.data[DATA_KEY][comp_or_platform] # type: ignore
+ return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore
except KeyError:
pass
- cache = hass.data.get(DATA_KEY)
+ cache = hass.data.get(DATA_COMPONENTS)
if cache is None:
- if hass.config.config_dir is None:
- _LOGGER.error("Can't load components - config dir is not set")
+ if not _async_mount_config_dir(hass):
return None
- # Only insert if it's not there (happens during tests)
- if sys.path[0] != hass.config.config_dir:
- sys.path.insert(0, hass.config.config_dir)
- cache = hass.data[DATA_KEY] = {}
+ cache = hass.data[DATA_COMPONENTS] = {}
for path in ('{}.{}'.format(base, comp_or_platform)
for base in base_paths):
@@ -194,17 +274,10 @@ def _load_file(hass, # type: HomeAssistant
if getattr(module, '__file__', None) is None:
continue
- _LOGGER.info("Loaded %s from %s", comp_or_platform, path)
-
cache[comp_or_platform] = module
if module.__name__.startswith(PACKAGE_CUSTOM_COMPONENTS):
- _LOGGER.warning(
- 'You are using a custom component for %s which has not '
- 'been tested by Home Assistant. This component might '
- 'cause stability problems, be sure to disable it if you '
- 'do experience issues with Home Assistant.',
- comp_or_platform)
+ _LOGGER.warning(CUSTOM_WARNING, comp_or_platform)
return module
@@ -261,9 +334,19 @@ class Components:
def __getattr__(self, comp_name: str) -> ModuleWrapper:
"""Fetch a component."""
- component = get_component(self._hass, comp_name)
+ # Test integration cache
+ integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name)
+
+ if isinstance(integration, Integration):
+ component = integration.get_component(
+ ) # type: Optional[ModuleType]
+ else:
+ # Fallback to importing old-school
+ component = _load_file(self._hass, comp_name, LOOKUP_PATHS)
+
if component is None:
raise ImportError('Unable to load {}'.format(comp_name))
+
wrapped = ModuleWrapper(self._hass, component)
setattr(self, comp_name, wrapped)
return wrapped
@@ -294,46 +377,55 @@ def bind_hass(func: CALLABLE_T) -> CALLABLE_T:
return func
-def component_dependencies(hass, # type: HomeAssistant
- comp_name: str) -> Set[str]:
+async def async_component_dependencies(hass, # type: HomeAssistant
+ domain: str) -> Set[str]:
"""Return all dependencies and subdependencies of components.
Raises CircularDependency if a circular dependency is found.
-
- Async friendly.
"""
- return _component_dependencies(hass, comp_name, set(), set())
+ return await _async_component_dependencies(hass, domain, set(), set())
-def _component_dependencies(hass, # type: HomeAssistant
- comp_name: str, loaded: Set[str],
- loading: Set) -> Set[str]:
+async def _async_component_dependencies(hass, # type: HomeAssistant
+ domain: str, loaded: Set[str],
+ loading: Set) -> Set[str]:
"""Recursive function to get component dependencies.
Async friendly.
"""
- component = get_component(hass, comp_name)
+ integration = await async_get_integration(hass, domain)
- if component is None:
- raise ComponentNotFound(comp_name)
+ loading.add(domain)
- loading.add(comp_name)
-
- for dependency in getattr(component, 'DEPENDENCIES', []):
+ for dependency_domain in integration.dependencies:
# Check not already loaded
- if dependency in loaded:
+ if dependency_domain in loaded:
continue
# If we are already loading it, we have a circular dependency.
- if dependency in loading:
- raise CircularDependency(comp_name, dependency)
+ if dependency_domain in loading:
+ raise CircularDependency(domain, dependency_domain)
- dep_loaded = _component_dependencies(
- hass, dependency, loaded, loading)
+ dep_loaded = await _async_component_dependencies(
+ hass, dependency_domain, loaded, loading)
loaded.update(dep_loaded)
- loaded.add(comp_name)
- loading.remove(comp_name)
+ loaded.add(domain)
+ loading.remove(domain)
return loaded
+
+
+def _async_mount_config_dir(hass, # type: HomeAssistant
+ ) -> bool:
+ """Mount config dir in order to load custom_component.
+
+ Async friendly but not a coroutine.
+ """
+ if hass.config.config_dir is None:
+ _LOGGER.error("Can't load components - config dir is not set")
+ return False
+ if hass.config.config_dir not in sys.path:
+ sys.path.insert(0, hass.config.config_dir)
+ return True
diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt
index 04704a00484..3bef086d70a 100644
--- a/homeassistant/package_constraints.txt
+++ b/homeassistant/package_constraints.txt
@@ -5,14 +5,14 @@ attrs==18.2.0
bcrypt==3.1.6
certifi>=2018.04.16
jinja2>=2.10
-PyJWT==1.6.4
-cryptography==2.5
+PyJWT==1.7.1
+cryptography==2.6.1
pip>=8.0.3
-python-slugify==1.2.6
-pytz>=2018.07
+python-slugify==3.0.2
+pytz>=2019.01
pyyaml>=3.13,<4
requests==2.21.0
-ruamel.yaml==0.15.89
+ruamel.yaml==0.15.91
voluptuous==0.11.5
voluptuous-serialize==2.1.0
diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py
index 1b8c6719395..4bdda85bc07 100644
--- a/homeassistant/scripts/check_config.py
+++ b/homeassistant/scripts/check_config.py
@@ -201,7 +201,8 @@ def check(config_dir, secrets=False):
hass = core.HomeAssistant()
hass.config.config_dir = config_dir
- res['components'] = check_ha_config_file(hass)
+ res['components'] = hass.loop.run_until_complete(
+ check_ha_config_file(hass))
res['secret_cache'] = OrderedDict(yaml.__SECRET_CACHE)
for err in res['components'].errors:
@@ -280,7 +281,7 @@ class HomeAssistantConfig(OrderedDict):
return self
-def check_ha_config_file(hass):
+async def check_ha_config_file(hass):
"""Check if Home Assistant configuration file is valid."""
config_dir = hass.config.config_dir
result = HomeAssistantConfig()
@@ -300,10 +301,12 @@ def check_ha_config_file(hass):
# Load configuration.yaml
try:
- config_path = find_config_file(config_dir)
+ config_path = await hass.async_add_executor_job(
+ find_config_file, config_dir)
if not config_path:
return result.add_error("File configuration.yaml not found.")
- config = load_yaml_config_file(config_path)
+ config = await hass.async_add_executor_job(
+ load_yaml_config_file, config_path)
except HomeAssistantError as err:
return result.add_error(
"Error loading {}: {}".format(config_path, err))
@@ -320,7 +323,7 @@ def check_ha_config_file(hass):
core_config = {}
# Merge packages
- merge_packages_config(
+ await merge_packages_config(
hass, config, core_config.get(CONF_PACKAGES, {}), _pack_error)
core_config.pop(CONF_PACKAGES, None)
@@ -329,8 +332,15 @@ def check_ha_config_file(hass):
# Process and validate config
for domain in components:
- component = loader.get_component(hass, domain)
- if not component:
+ try:
+ integration = await loader.async_get_integration(hass, domain)
+ except loader.IntegrationNotFound:
+ result.add_error("Integration not found: {}".format(domain))
+ continue
+
+ try:
+ component = integration.get_component()
+ except ImportError:
result.add_error("Component not found: {}".format(domain))
continue
@@ -342,21 +352,19 @@ def check_ha_config_file(hass):
_comp_error(ex, domain, config)
continue
- if (not hasattr(component, 'PLATFORM_SCHEMA') and
- not hasattr(component, 'PLATFORM_SCHEMA_BASE')):
+ component_platform_schema = getattr(
+ component, 'PLATFORM_SCHEMA_BASE',
+ getattr(component, 'PLATFORM_SCHEMA', None))
+
+ if component_platform_schema is None:
continue
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
- if hasattr(component, 'PLATFORM_SCHEMA_BASE'):
- p_validated = \
- component.PLATFORM_SCHEMA_BASE( # type: ignore
- p_config)
- else:
- p_validated = component.PLATFORM_SCHEMA( # type: ignore
- p_config)
+ p_validated = component_platform_schema( # type: ignore
+ p_config)
except vol.Invalid as ex:
_comp_error(ex, domain, config)
continue
@@ -368,9 +376,18 @@ def check_ha_config_file(hass):
platforms.append(p_validated)
continue
- platform = loader.get_platform(hass, domain, p_name)
+ try:
+ p_integration = await loader.async_get_integration(hass,
+ p_name)
+ except loader.IntegrationNotFound:
+ result.add_error(
+ "Integration {} not found when trying to verify its {} "
+ "platform.".format(p_name, domain))
+ continue
- if platform is None:
+ try:
+ platform = p_integration.get_platform(domain)
+ except ImportError:
result.add_error(
"Platform not found: {}.{}".format(domain, p_name))
continue
diff --git a/homeassistant/setup.py b/homeassistant/setup.py
index 29c8e22d45d..05e3307299a 100644
--- a/homeassistant/setup.py
+++ b/homeassistant/setup.py
@@ -24,14 +24,14 @@ SLOW_SETUP_WARNING = 10
def setup_component(hass: core.HomeAssistant, domain: str,
- config: Optional[Dict] = None) -> bool:
+ config: Dict) -> bool:
"""Set up a component and all its dependencies."""
return run_coroutine_threadsafe( # type: ignore
async_setup_component(hass, domain, config), loop=hass.loop).result()
async def async_setup_component(hass: core.HomeAssistant, domain: str,
- config: Optional[Dict] = None) -> bool:
+ config: Dict) -> bool:
"""Set up a component and all its dependencies.
This method is a coroutine.
@@ -39,17 +39,11 @@ async def async_setup_component(hass: core.HomeAssistant, domain: str,
if domain in hass.config.components:
return True
- setup_tasks = hass.data.get(DATA_SETUP)
+ setup_tasks = hass.data.setdefault(DATA_SETUP, {})
- if setup_tasks is not None and domain in setup_tasks:
+ if domain in setup_tasks:
return await setup_tasks[domain] # type: ignore
- if config is None:
- config = {}
-
- if setup_tasks is None:
- setup_tasks = hass.data[DATA_SETUP] = {}
-
task = setup_tasks[domain] = hass.async_create_task(
_async_setup_component(hass, domain, config))
@@ -100,16 +94,16 @@ async def _async_setup_component(hass: core.HomeAssistant,
_LOGGER.error("Setup failed for %s: %s", domain, msg)
async_notify_setup_error(hass, domain, link)
- component = loader.get_component(hass, domain)
-
- if not component:
- log_error("Component not found.", False)
+ try:
+ integration = await loader.async_get_integration(hass, domain)
+ except loader.IntegrationNotFound:
+ log_error("Integration not found.", False)
return False
# Validate all dependencies exist and there are no circular dependencies
try:
- loader.component_dependencies(hass, domain)
- except loader.ComponentNotFound as err:
+ await loader.async_component_dependencies(hass, domain)
+ except loader.IntegrationNotFound as err:
_LOGGER.error(
"Not setting up %s because we are unable to resolve "
"(sub)dependency %s", domain, err.domain)
@@ -120,22 +114,30 @@ async def _async_setup_component(hass: core.HomeAssistant,
"%s -> %s", domain, err.from_domain, err.to_domain)
return False
- processed_config = \
- conf_util.async_process_component_config(hass, config, domain)
+ # Process requirements as soon as possible, so we can import the component
+ # without requiring imports to be in functions.
+ try:
+ await async_process_deps_reqs(hass, config, integration)
+ except HomeAssistantError as err:
+ log_error(str(err))
+ return False
+
+ processed_config = await conf_util.async_process_component_config(
+ hass, config, integration)
if processed_config is None:
log_error("Invalid config.")
return False
- try:
- await async_process_deps_reqs(hass, config, domain, component)
- except HomeAssistantError as err:
- log_error(str(err))
- return False
-
start = timer()
_LOGGER.info("Setting up %s", domain)
+ try:
+ component = integration.get_component()
+ except ImportError:
+ log_error("Unable to import component", False)
+ return False
+
if hasattr(component, 'PLATFORM_SCHEMA'):
# Entity components have their own warning
warn_task = None
@@ -168,12 +170,11 @@ async def _async_setup_component(hass: core.HomeAssistant,
if result is not True:
log_error("Component {!r} did not return boolean if setup was "
"successful. Disabling component.".format(domain))
- loader.set_component(hass, domain, None)
return False
if hass.config_entries:
for entry in hass.config_entries.async_entries(domain):
- await entry.async_setup(hass, component=component)
+ await entry.async_setup(hass, integration=integration)
hass.config.components.add(component.DOMAIN) # type: ignore
@@ -183,13 +184,14 @@ async def _async_setup_component(hass: core.HomeAssistant,
hass.bus.async_fire(
EVENT_COMPONENT_LOADED,
- {ATTR_COMPONENT: component.DOMAIN} # type: ignore
+ {ATTR_COMPONENT: component.DOMAIN} # type: ignore
)
return True
-async def async_prepare_setup_platform(hass: core.HomeAssistant, config: Dict,
+async def async_prepare_setup_platform(hass: core.HomeAssistant,
+ hass_config: Dict,
domain: str, platform_name: str) \
-> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup.
@@ -202,13 +204,26 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config: Dict,
def log_error(msg: str) -> None:
"""Log helper."""
_LOGGER.error("Unable to prepare setup for platform %s: %s",
- platform_path, msg)
+ platform_name, msg)
async_notify_setup_error(hass, platform_path)
- platform = loader.get_platform(hass, domain, platform_name)
+ try:
+ integration = await loader.async_get_integration(hass, platform_name)
+ except loader.IntegrationNotFound:
+ log_error("Integration not found")
+ return None
- # Not found
- if platform is None:
+ # Process deps and reqs as soon as possible, so that requirements are
+ # available when we import the platform.
+ try:
+ await async_process_deps_reqs(hass, hass_config, integration)
+ except HomeAssistantError as err:
+ log_error(str(err))
+ return None
+
+ try:
+ platform = integration.get_platform(domain)
+ except ImportError:
log_error("Platform not found.")
return None
@@ -216,19 +231,29 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config: Dict,
if platform_path in hass.config.components:
return platform
- try:
- await async_process_deps_reqs(
- hass, config, platform_path, platform)
- except HomeAssistantError as err:
- log_error(str(err))
- return None
+ # Platforms cannot exist on their own, they are part of their integration.
+ # If the integration is not set up yet, and can be set up, set it up.
+ if integration.domain not in hass.config.components:
+ try:
+ component = integration.get_component()
+ except ImportError:
+ log_error("Unable to import the component")
+ return None
+
+ if (hasattr(component, 'setup')
+ or hasattr(component, 'async_setup')):
+ if not await async_setup_component(
+ hass, integration.domain, hass_config
+ ):
+ log_error("Unable to set up component.")
+ return None
return platform
async def async_process_deps_reqs(
- hass: core.HomeAssistant, config: Dict, name: str,
- module: ModuleType) -> None:
+ hass: core.HomeAssistant, config: Dict,
+ integration: loader.Integration) -> None:
"""Process all dependencies and requirements for a module.
Module is a Python module of either a component or platform.
@@ -237,24 +262,23 @@ async def async_process_deps_reqs(
if processed is None:
processed = hass.data[DATA_DEPS_REQS] = set()
- elif name in processed:
+ elif integration.domain in processed:
return
- if hasattr(module, 'DEPENDENCIES'):
- dep_success = await _async_process_dependencies(
- hass, config, name, module.DEPENDENCIES) # type: ignore
+ if integration.dependencies and not await _async_process_dependencies(
+ hass,
+ config,
+ integration.domain,
+ integration.dependencies
+ ):
+ raise HomeAssistantError("Could not set up all dependencies.")
- if not dep_success:
- raise HomeAssistantError("Could not set up all dependencies.")
+ if (not hass.config.skip_pip and integration.requirements and
+ not await requirements.async_process_requirements(
+ hass, integration.domain, integration.requirements)):
+ raise HomeAssistantError("Could not install all requirements.")
- if not hass.config.skip_pip and hasattr(module, 'REQUIREMENTS'):
- req_success = await requirements.async_process_requirements(
- hass, name, module.REQUIREMENTS) # type: ignore
-
- if not req_success:
- raise HomeAssistantError("Could not install all requirements.")
-
- processed.add(name)
+ processed.add(integration.domain)
@core.callback
diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py
index 15bf73f459d..f6d967b6e5a 100644
--- a/homeassistant/util/yaml.py
+++ b/homeassistant/util/yaml.py
@@ -157,6 +157,8 @@ def _include_dir_named_yaml(loader: SafeLineLoader,
loc = os.path.join(os.path.dirname(loader.name), node.value)
for fname in _find_files(loc, '*.yaml'):
filename = os.path.splitext(os.path.basename(fname))[0]
+ if os.path.basename(fname) == SECRET_YAML:
+ continue
mapping[filename] = load_yaml(fname)
return _add_reference(mapping, loader, node)
diff --git a/requirements_all.txt b/requirements_all.txt
index 231637fef8d..8772528c520 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -6,45 +6,45 @@ attrs==18.2.0
bcrypt==3.1.6
certifi>=2018.04.16
jinja2>=2.10
-PyJWT==1.6.4
-cryptography==2.5
+PyJWT==1.7.1
+cryptography==2.6.1
pip>=8.0.3
-python-slugify==1.2.6
-pytz>=2018.07
+python-slugify==3.0.2
+pytz>=2019.01
pyyaml>=3.13,<4
requests==2.21.0
-ruamel.yaml==0.15.89
+ruamel.yaml==0.15.91
voluptuous==0.11.5
voluptuous-serialize==2.1.0
# homeassistant.components.nuimo_controller
--only-binary=all nuimo==0.1.0
-# homeassistant.components.dht.sensor
+# homeassistant.components.dht
# Adafruit-DHT==1.4.0
-# homeassistant.components.sht31.sensor
+# homeassistant.components.sht31
Adafruit-GPIO==1.0.3
-# homeassistant.components.sht31.sensor
+# homeassistant.components.sht31
Adafruit-SHT31==1.0.2
# homeassistant.components.bbb_gpio
# Adafruit_BBIO==1.0.0
# homeassistant.components.homekit
-HAP-python==2.4.2
+HAP-python==2.5.0
-# homeassistant.components.mastodon.notify
+# homeassistant.components.mastodon
Mastodon.py==1.3.1
-# homeassistant.components.github.sensor
+# homeassistant.components.github
PyGithub==1.43.5
# homeassistant.components.isy994
PyISY==1.1.1
-# homeassistant.components.mvglive.sensor
+# homeassistant.components.mvglive
PyMVGLive==1.1.4
# homeassistant.components.arduino
@@ -57,13 +57,13 @@ PyNaCl==1.3.0
# homeassistant.auth.mfa_modules.totp
PyQRCode==1.2.1
-# homeassistant.components.rmvtransport.sensor
+# homeassistant.components.rmvtransport
PyRMVtransport==0.1.3
-# homeassistant.components.switchbot.switch
+# homeassistant.components.switchbot
# PySwitchbot==0.5
-# homeassistant.components.transport_nsw.sensor
+# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1
# homeassistant.components.xiaomi_aqara
@@ -75,53 +75,53 @@ PyXiaomiGateway==0.12.2
# homeassistant.components.remember_the_milk
RtmAPI==0.7.0
-# homeassistant.components.travisci.sensor
+# homeassistant.components.travisci
TravisPy==0.3.5
-# homeassistant.components.twitter.notify
+# homeassistant.components.twitter
TwitterAPI==2.5.9
-# homeassistant.components.tof.sensor
+# homeassistant.components.tof
# VL53L1X2==0.1.5
-# homeassistant.components.waze_travel_time.sensor
+# homeassistant.components.waze_travel_time
WazeRouteCalculator==0.9
-# homeassistant.components.yessssms.notify
+# homeassistant.components.yessssms
YesssSMS==0.2.3
# homeassistant.components.abode
abodepy==0.15.0
-# homeassistant.components.frontier_silicon.media_player
+# homeassistant.components.frontier_silicon
afsapi==0.0.4
# homeassistant.components.ambient_station
-aioambient==0.2.0
+aioambient==0.3.0
# homeassistant.components.asuswrt
aioasuswrt==1.1.21
-# homeassistant.components.automatic.device_tracker
+# homeassistant.components.automatic
aioautomatic==0.6.5
# homeassistant.components.aws
aiobotocore==0.10.2
-# homeassistant.components.dnsip.sensor
+# homeassistant.components.dnsip
aiodns==1.1.1
# homeassistant.components.esphome
-aioesphomeapi==1.7.0
+aioesphomeapi==2.0.1
# homeassistant.components.freebox
aiofreepybox==0.0.8
-# homeassistant.components.yi.camera
+# homeassistant.components.yi
aioftp==0.12.0
-# homeassistant.components.harmony.remote
-aioharmony==0.1.8
+# homeassistant.components.harmony
+aioharmony==0.1.11
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -130,82 +130,85 @@ aiohttp_cors==0.7.0
# homeassistant.components.hue
aiohue==1.9.1
-# homeassistant.components.imap.sensor
+# homeassistant.components.imap
aioimaplib==0.7.15
# homeassistant.components.lifx
aiolifx==0.6.7
-# homeassistant.components.lifx.light
+# homeassistant.components.lifx
aiolifx_effects==0.2.1
-# homeassistant.components.hunterdouglas_powerview.scene
+# homeassistant.components.hunterdouglas_powerview
aiopvapi==1.6.14
# homeassistant.components.unifi
aiounifi==4
-# homeassistant.components.aladdin_connect.cover
+# homeassistant.components.aladdin_connect
aladdin_connect==0.3
# homeassistant.components.alarmdecoder
alarmdecoder==1.13.2
-# homeassistant.components.alpha_vantage.sensor
+# homeassistant.components.alpha_vantage
alpha_vantage==2.1.0
# homeassistant.components.amcrest
-amcrest==1.2.7
+amcrest==1.3.0
-# homeassistant.components.androidtv.media_player
-androidtv==0.0.14
+# homeassistant.components.androidtv
+androidtv==0.0.15
-# homeassistant.components.anel_pwrctrl.switch
+# homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2
-# homeassistant.components.anthemav.media_player
+# homeassistant.components.anthemav
anthemav==1.1.10
# homeassistant.components.apcupsd
apcaccess==0.0.13
-# homeassistant.components.apns.notify
+# homeassistant.components.apns
apns2==0.3.0
# homeassistant.components.aqualogic
aqualogic==1.0
+# homeassistant.components.ampio
+asmog==0.0.6
+
# homeassistant.components.asterisk_mbox
asterisk_mbox==0.5.0
+# homeassistant.components.dlna_dmr
# homeassistant.components.upnp
-# homeassistant.components.dlna_dmr.media_player
async-upnp-client==0.14.7
# homeassistant.components.stream
av==6.1.2
-# homeassistant.components.avion.light
+# homeassistant.components.avion
# avion==0.10
# homeassistant.components.axis
-axis==17
+axis==22
-# homeassistant.components.baidu.tts
+# homeassistant.components.baidu
baidu-aip==1.6.6
-# homeassistant.components.modem_callerid.sensor
+# homeassistant.components.modem_callerid
basicmodem==0.7
-# homeassistant.components.linux_battery.sensor
+# homeassistant.components.linux_battery
batinfo==0.4.2
-# homeassistant.components.eddystone_temperature.sensor
+# homeassistant.components.eddystone_temperature
# beacontools[scan]==1.2.3
-# homeassistant.components.linksys_ap.device_tracker
-# homeassistant.components.scrape.sensor
-# homeassistant.components.sytadin.sensor
+# homeassistant.components.linksys_ap
+# homeassistant.components.scrape
+# homeassistant.components.sytadin
beautifulsoup4==4.7.1
# homeassistant.components.zha
@@ -217,151 +220,139 @@ bimmer_connected==0.5.3
# homeassistant.components.blink
blinkpy==0.13.1
-# homeassistant.components.blinksticklight.light
+# homeassistant.components.blinksticklight
blinkstick==1.1.8
-# homeassistant.components.blinkt.light
+# homeassistant.components.blinkt
# blinkt==0.1.0
-# homeassistant.components.bitcoin.sensor
+# homeassistant.components.bitcoin
blockchain==1.4.4
-# homeassistant.components.decora.light
+# homeassistant.components.decora
# bluepy==1.1.4
-# homeassistant.components.bme680.sensor
+# homeassistant.components.bme680
# bme680==1.0.5
+# homeassistant.components.bom
+bomradarloop==0.1.2
+
+# homeassistant.components.amazon_polly
# homeassistant.components.route53
-# homeassistant.components.amazon_polly.tts
-# homeassistant.components.aws_lambda.notify
-# homeassistant.components.aws_sns.notify
-# homeassistant.components.aws_sqs.notify
boto3==1.9.16
-# homeassistant.components.braviatv.media_player
+# homeassistant.components.braviatv
braviarc-homeassistant==0.3.7.dev0
-# homeassistant.components.broadlink.sensor
-# homeassistant.components.broadlink.switch
+# homeassistant.components.broadlink
broadlink==0.9.0
-# homeassistant.components.brottsplatskartan.sensor
+# homeassistant.components.brottsplatskartan
brottsplatskartan==0.0.1
-# homeassistant.components.brunt.cover
+# homeassistant.components.brunt
brunt==0.1.3
-# homeassistant.components.bluetooth_tracker.device_tracker
+# homeassistant.components.bluetooth_tracker
bt_proximity==0.1.2
-# homeassistant.components.bt_home_hub_5.device_tracker
+# homeassistant.components.bt_home_hub_5
bthomehub5-devicelist==0.1.1
-# homeassistant.components.bt_smarthub.device_tracker
+# homeassistant.components.bt_smarthub
btsmarthub_devicelist==0.1.3
-# homeassistant.components.buienradar.sensor
-# homeassistant.components.buienradar.weather
+# homeassistant.components.buienradar
buienradar==0.91
-# homeassistant.components.caldav.calendar
-caldav==0.5.0
+# homeassistant.components.caldav
+caldav==0.6.1
-# homeassistant.components.cisco_mobility_express.device_tracker
+# homeassistant.components.cisco_mobility_express
ciscomobilityexpress==0.1.5
-# homeassistant.components.ciscospark.notify
+# homeassistant.components.ciscospark
ciscosparkapi==0.4.2
-# homeassistant.components.cppm_tracker.device_tracker
+# homeassistant.components.cppm_tracker
clearpasspy==1.0.2
-# homeassistant.components.co2signal.sensor
+# homeassistant.components.co2signal
co2signal==0.4.2
# homeassistant.components.coinbase
coinbase==2.1.0
-# homeassistant.components.coinmarketcap.sensor
+# homeassistant.components.coinmarketcap
coinmarketcap==5.0.3
# homeassistant.scripts.check_config
colorlog==4.0.2
-# homeassistant.components.concord232.alarm_control_panel
-# homeassistant.components.concord232.binary_sensor
+# homeassistant.components.concord232
concord232==0.15
-# homeassistant.components.eddystone_temperature.sensor
-# homeassistant.components.eq3btsmart.climate
-# homeassistant.components.xiaomi_miio.device_tracker
-# homeassistant.components.xiaomi_miio.fan
-# homeassistant.components.xiaomi_miio.light
-# homeassistant.components.xiaomi_miio.remote
-# homeassistant.components.xiaomi_miio.sensor
-# homeassistant.components.xiaomi_miio.switch
-# homeassistant.components.xiaomi_miio.vacuum
+# homeassistant.components.eddystone_temperature
+# homeassistant.components.eq3btsmart
+# homeassistant.components.xiaomi_miio
construct==2.9.45
# homeassistant.scripts.credstash
# credstash==1.15.0
-# homeassistant.components.crimereports.sensor
+# homeassistant.components.crimereports
crimereports==1.0.1
# homeassistant.components.datadog
datadog==0.15.0
-# homeassistant.components.metoffice.sensor
-# homeassistant.components.metoffice.weather
+# homeassistant.components.metoffice
datapoint==0.4.3
-# homeassistant.components.decora.light
+# homeassistant.components.decora
# decora==0.6
-# homeassistant.components.decora_wifi.light
-# decora_wifi==1.3
+# homeassistant.components.decora_wifi
+# decora_wifi==1.4
# homeassistant.components.ihc
# homeassistant.components.namecheapdns
-# homeassistant.components.ohmconnect.sensor
-# homeassistant.components.upc_connect.device_tracker
+# homeassistant.components.ohmconnect
+# homeassistant.components.upc_connect
defusedxml==0.5.0
-# homeassistant.components.deluge.sensor
-# homeassistant.components.deluge.switch
+# homeassistant.components.deluge
deluge-client==1.4.0
-# homeassistant.components.denonavr.media_player
+# homeassistant.components.denonavr
denonavr==0.7.8
-# homeassistant.components.directv.media_player
+# homeassistant.components.directv
directpy==0.5
-# homeassistant.components.discogs.sensor
+# homeassistant.components.discogs
discogs_client==2.2.1
-# homeassistant.components.discord.notify
+# homeassistant.components.discord
discord.py==0.16.12
# homeassistant.components.updater
distro==1.4.0
-# homeassistant.components.digitalloggers.switch
+# homeassistant.components.digitalloggers
dlipower==0.7.165
# homeassistant.components.doorbird
-doorbirdpy==2.0.6
+doorbirdpy==2.0.8
# homeassistant.components.dovado
dovado==0.4.1
-# homeassistant.components.dsmr.sensor
+# homeassistant.components.dsmr
dsmr_parser==0.12
# homeassistant.components.dweet
-# homeassistant.components.dweet.sensor
dweepy==0.3.0
# homeassistant.components.ebusd
@@ -373,10 +364,10 @@ ecoaliface==0.4.0
# homeassistant.components.edp_redy
edp_redy==0.0.3
-# homeassistant.components.ee_brightbox.device_tracker
+# homeassistant.components.ee_brightbox
eebrightbox==0.0.4
-# homeassistant.components.eliqonline.sensor
+# homeassistant.components.eliqonline
eliqonline==1.2.2
# homeassistant.components.elkm1
@@ -388,39 +379,42 @@ emulated_roku==0.1.8
# homeassistant.components.enocean
enocean==0.40
-# homeassistant.components.entur_public_transport.sensor
+# homeassistant.components.entur_public_transport
enturclient==0.2.0
-# homeassistant.components.envirophat.sensor
+# homeassistant.components.envirophat
# envirophat==0.0.6
-# homeassistant.components.enphase_envoy.sensor
+# homeassistant.components.enphase_envoy
envoy_reader==0.3
-# homeassistant.components.season.sensor
+# homeassistant.components.season
ephem==3.7.6.0
-# homeassistant.components.epson.media_player
+# homeassistant.components.epson
epson-projector==0.1.3
+# homeassistant.components.epsonworkforce
+epsonprinter==0.0.8
+
# homeassistant.components.netgear_lte
-eternalegypt==0.0.5
+eternalegypt==0.0.7
# homeassistant.components.keyboard_remote
# evdev==0.6.1
# homeassistant.components.evohome
-# homeassistant.components.honeywell.climate
-evohomeclient==0.2.8
+# homeassistant.components.honeywell
+evohomeclient==0.3.2
-# homeassistant.components.dlib_face_detect.image_processing
-# homeassistant.components.dlib_face_identify.image_processing
+# homeassistant.components.dlib_face_detect
+# homeassistant.components.dlib_face_identify
# face_recognition==1.2.3
# homeassistant.components.fastdotcom
fastdotcom==0.0.3
-# homeassistant.components.fedex.sensor
+# homeassistant.components.fedex
fedexdeliverymanager==1.0.6
# homeassistant.components.feedreader
@@ -429,56 +423,62 @@ feedparser-homeassistant==5.2.2.dev1
# homeassistant.components.fibaro
fiblary3==0.1.7
-# homeassistant.components.fints.sensor
+# homeassistant.components.fints
fints==1.0.1
-# homeassistant.components.fitbit.sensor
+# homeassistant.components.fitbit
fitbit==0.3.0
-# homeassistant.components.fixer.sensor
+# homeassistant.components.fixer
fixerio==1.0.0a0
-# homeassistant.components.flux_led.light
+# homeassistant.components.flux_led
flux_led==0.22
-# homeassistant.components.foobot.sensor
+# homeassistant.components.foobot
foobot_async==0.3.1
-# homeassistant.components.free_mobile.notify
+# homeassistant.components.free_mobile
freesms==0.1.2
-# homeassistant.components.fritz.device_tracker
-# homeassistant.components.fritzbox_callmonitor.sensor
-# homeassistant.components.fritzbox_netmonitor.sensor
+# homeassistant.components.fritz
+# homeassistant.components.fritzbox_callmonitor
+# homeassistant.components.fritzbox_netmonitor
# fritzconnection==0.6.5
-# homeassistant.components.fritzdect.switch
+# homeassistant.components.fritzdect
fritzhome==1.0.4
-# homeassistant.components.google.tts
+# homeassistant.components.google_translate
gTTS-token==1.1.3
-# homeassistant.components.gearbest.sensor
+# homeassistant.components.gearbest
gearbest_parser==1.0.7
-# homeassistant.components.geizhals.sensor
+# homeassistant.components.geizhals
geizhals==0.0.9
-# homeassistant.components.geo_json_events.geo_location
-# homeassistant.components.nsw_rural_fire_service_feed.geo_location
-# homeassistant.components.usgs_earthquakes_feed.geo_location
+# homeassistant.components.geniushub
+geniushub-client==0.3.6
+
+# homeassistant.components.geo_json_events
+# homeassistant.components.nsw_rural_fire_service_feed
+# homeassistant.components.usgs_earthquakes_feed
geojson_client==0.3
-# homeassistant.components.geo_rss_events.sensor
-georss_client==0.5
+# homeassistant.components.geo_rss_events
+georss_generic_client==0.2
-# homeassistant.components.gitter.sensor
+# homeassistant.components.ign_sismologia
+georss_ign_sismologia_client==0.2
+
+# homeassistant.components.gitter
gitterpy==0.1.7
-# homeassistant.components.glances.sensor
+# homeassistant.components.glances
glances_api==0.2.0
-# homeassistant.components.gntp.notify
+# homeassistant.components.gntp
gntp==1.0.3
# homeassistant.components.google
@@ -490,25 +490,25 @@ google-cloud-pubsub==0.39.1
# homeassistant.components.googlehome
googledevices==1.0.2
-# homeassistant.components.google_travel_time.sensor
+# homeassistant.components.google_travel_time
googlemaps==2.5.1
-# homeassistant.components.gpsd.sensor
+# homeassistant.components.gpsd
gps3==0.33.3
# homeassistant.components.greeneye_monitor
greeneye_monitor==1.0
-# homeassistant.components.greenwave.light
+# homeassistant.components.greenwave
greenwavereality==0.5.1
-# homeassistant.components.gstreamer.media_player
+# homeassistant.components.gstreamer
gstreamer-player==1.1.2
# homeassistant.components.ffmpeg
ha-ffmpeg==2.0
-# homeassistant.components.philips_js.media_player
+# homeassistant.components.philips_js
ha-philipsjs==0.0.5
# homeassistant.components.habitica
@@ -518,48 +518,48 @@ habitipy==0.2.0
hangups==0.4.6
# homeassistant.components.cloud
-hass-nabucasa==0.11
+hass-nabucasa==0.12
-# homeassistant.components.mqtt.server
+# homeassistant.components.mqtt
hbmqtt==0.9.4
-# homeassistant.components.jewish_calendar.sensor
+# homeassistant.components.jewish_calendar
hdate==0.8.7
-# homeassistant.components.heatmiser.climate
+# homeassistant.components.heatmiser
heatmiserV3==0.9.1
-# homeassistant.components.hikvisioncam.switch
+# homeassistant.components.hikvisioncam
hikvision==0.4
-# homeassistant.components.hipchat.notify
+# homeassistant.components.hipchat
hipnotify==1.0.8
-# homeassistant.components.harman_kardon_avr.media_player
+# homeassistant.components.harman_kardon_avr
hkavr==0.0.5
# homeassistant.components.hlk_sw16
hlk-sw16==0.0.7
-# homeassistant.components.pi_hole.sensor
+# homeassistant.components.pi_hole
hole==0.3.0
-# homeassistant.components.workday.binary_sensor
+# homeassistant.components.workday
holidays==0.9.10
# homeassistant.components.frontend
-home-assistant-frontend==20190331.0
+home-assistant-frontend==20190424.0
# homeassistant.components.zwave
-homeassistant-pyozw==0.1.3
+homeassistant-pyozw==0.1.4
# homeassistant.components.homekit_controller
homekit[IP]==0.13.0
# homeassistant.components.homematicip_cloud
-homematicip==0.10.6
+homematicip==0.10.7
-# homeassistant.components.horizon.media_player
+# homeassistant.components.horizon
horimote==0.4.1
# homeassistant.components.google
@@ -572,22 +572,21 @@ huawei-lte-api==1.1.5
# homeassistant.components.hydrawise
hydrawiser==0.1.1
-# homeassistant.components.bh1750.sensor
-# homeassistant.components.bme280.sensor
-# homeassistant.components.htu21d.sensor
+# homeassistant.components.bh1750
+# homeassistant.components.bme280
+# homeassistant.components.htu21d
# i2csense==0.0.4
# homeassistant.components.watson_iot
ibmiotf==0.3.4
-# homeassistant.components.iglo.light
+# homeassistant.components.iglo
iglo==1.2.7
# homeassistant.components.ihc
ihcsdk==2.3.0
# homeassistant.components.influxdb
-# homeassistant.components.influxdb.sensor
influxdb==5.2.0
# homeassistant.components.insteon
@@ -602,11 +601,10 @@ ipify==1.0.0
# homeassistant.components.verisure
jsonpath==0.75
-# homeassistant.components.kodi.media_player
-# homeassistant.components.kodi.notify
+# homeassistant.components.kodi
jsonrpc-async==0.6
-# homeassistant.components.kodi.media_player
+# homeassistant.components.kodi
jsonrpc-websocket==0.6
# homeassistant.scripts.keyring
@@ -615,7 +613,7 @@ keyring==17.1.1
# homeassistant.scripts.keyring
keyrings.alt==3.1.1
-# homeassistant.components.kiwi.lock
+# homeassistant.components.kiwi
kiwiki-client==0.1.1
# homeassistant.components.konnected
@@ -625,46 +623,45 @@ konnected==0.1.5
lakeside==0.12
# homeassistant.components.dyson
-libpurecoollink==0.4.2
+libpurecool==0.5.0
-# homeassistant.components.foscam.camera
+# homeassistant.components.foscam
libpyfoscam==1.0
-# homeassistant.components.mikrotik.device_tracker
+# homeassistant.components.mikrotik
librouteros==2.2.0
-# homeassistant.components.soundtouch.media_player
+# homeassistant.components.soundtouch
libsoundtouch==0.7.2
-# homeassistant.components.lifx_legacy.light
+# homeassistant.components.lifx_legacy
liffylights==0.9.4
-# homeassistant.components.osramlightify.light
-lightify==1.0.6.1
+# homeassistant.components.osramlightify
+lightify==1.0.7.2
# homeassistant.components.lightwave
lightwave==0.15
-# homeassistant.components.limitlessled.light
+# homeassistant.components.limitlessled
limitlessled==1.1.3
# homeassistant.components.linode
linode-api==4.1.9b1
-# homeassistant.components.liveboxplaytv.media_player
+# homeassistant.components.liveboxplaytv
liveboxplaytv==2.0.2
# homeassistant.components.lametric
-# homeassistant.components.lametric.notify
lmnotify==0.0.4
-# homeassistant.components.google_maps.device_tracker
+# homeassistant.components.google_maps
locationsharinglib==3.0.11
# homeassistant.components.logi_circle
-logi_circle==0.1.7
+logi_circle==0.2.2
-# homeassistant.components.london_underground.sensor
+# homeassistant.components.london_underground
london-tube-status==0.2
# homeassistant.components.luftdaten
@@ -673,13 +670,13 @@ luftdaten==0.3.4
# homeassistant.components.lupusec
lupupy==0.0.17
-# homeassistant.components.lw12wifi.light
+# homeassistant.components.lw12wifi
lw12==0.9.2
-# homeassistant.components.lyft.sensor
+# homeassistant.components.lyft
lyft_rides==0.2
-# homeassistant.components.magicseaweed.sensor
+# homeassistant.components.magicseaweed
magicseaweed==1.0.3
# homeassistant.components.matrix
@@ -691,23 +688,22 @@ maxcube-api==0.1.0
# homeassistant.components.mythicbeastsdns
mbddns==0.1.2
-# homeassistant.components.message_bird.notify
+# homeassistant.components.message_bird
messagebird==1.2.0
# homeassistant.components.meteo_france
meteofrance==0.3.4
-# homeassistant.components.mfi.sensor
-# homeassistant.components.mfi.switch
+# homeassistant.components.mfi
mficlient==0.3.0
-# homeassistant.components.miflora.sensor
+# homeassistant.components.miflora
miflora==0.4.0
-# homeassistant.components.mill.climate
+# homeassistant.components.mill
millheater==0.3.4
-# homeassistant.components.mitemp_bt.sensor
+# homeassistant.components.mitemp_bt
mitemp_bt==0.0.1
# homeassistant.components.mopar
@@ -725,95 +721,101 @@ mycroftapi==2.0
# homeassistant.components.usps
myusps==1.3.2
-# homeassistant.components.nad.media_player
+# homeassistant.components.n26
+n26==0.2.7
+
+# homeassistant.components.nad
nad_receiver==0.0.11
-# homeassistant.components.keenetic_ndms2.device_tracker
+# homeassistant.components.keenetic_ndms2
ndms2_client==0.0.6
# homeassistant.components.ness_alarm
nessclient==0.9.15
-# homeassistant.components.netdata.sensor
+# homeassistant.components.netdata
netdata==0.1.2
# homeassistant.components.discovery
-netdisco==2.5.0
+netdisco==2.6.0
-# homeassistant.components.neurio_energy.sensor
+# homeassistant.components.neurio_energy
neurio==0.3.1
-# homeassistant.components.niko_home_control.light
-niko-home-control==0.1.8
+# homeassistant.components.niko_home_control
+niko-home-control==0.2.1
-# homeassistant.components.nilu.air_quality
+# homeassistant.components.nilu
niluclient==0.1.2
-# homeassistant.components.nederlandse_spoorwegen.sensor
+# homeassistant.components.nederlandse_spoorwegen
nsapi==2.7.4
-# homeassistant.components.nsw_fuel_station.sensor
+# homeassistant.components.nsw_fuel_station
nsw-fuel-api-client==1.0.10
# homeassistant.components.nuheat
nuheat==0.3.0
-# homeassistant.components.opencv.image_processing
-# homeassistant.components.pollen.sensor
-# homeassistant.components.tensorflow.image_processing
-# homeassistant.components.trend.binary_sensor
+# homeassistant.components.opencv
+# homeassistant.components.pollen
+# homeassistant.components.tensorflow
+# homeassistant.components.trend
numpy==1.16.2
+# homeassistant.components.oasa_telematics
+oasatelematics==0.3
+
# homeassistant.components.google
oauth2client==4.0.0
-# homeassistant.components.oem.climate
+# homeassistant.components.oem
oemthermostat==1.1
-# homeassistant.components.onkyo.media_player
+# homeassistant.components.onkyo
onkyo-eiscp==1.2.4
-# homeassistant.components.onvif.camera
+# homeassistant.components.onvif
onvif-py3==0.1.3
-# homeassistant.components.openevse.sensor
+# homeassistant.components.openevse
openevsewifi==0.4
-# homeassistant.components.openhome.media_player
+# homeassistant.components.openhome
openhomedevice==0.4.2
-# homeassistant.components.opensensemap.air_quality
+# homeassistant.components.opensensemap
opensensemap-api==0.1.5
-# homeassistant.components.enigma2.media_player
-openwebifpy==3.1.0
+# homeassistant.components.enigma2
+openwebifpy==3.1.1
-# homeassistant.components.luci.device_tracker
+# homeassistant.components.luci
openwrt-luci-rpc==1.0.5
-# homeassistant.components.orvibo.switch
+# homeassistant.components.orvibo
orvibo==1.1.1
# homeassistant.components.mqtt
# homeassistant.components.shiftr
paho-mqtt==1.4.0
-# homeassistant.components.panasonic_bluray.media_player
+# homeassistant.components.panasonic_bluray
panacotta==0.1
-# homeassistant.components.panasonic_viera.media_player
+# homeassistant.components.panasonic_viera
panasonic_viera==0.3.2
-# homeassistant.components.dunehd.media_player
+# homeassistant.components.dunehd
pdunehd==1.3
-# homeassistant.components.pencom.switch
+# homeassistant.components.pencom
pencompy==0.0.3
-# homeassistant.components.aruba.device_tracker
-# homeassistant.components.cisco_ios.device_tracker
-# homeassistant.components.pandora.media_player
-# homeassistant.components.unifi_direct.device_tracker
+# homeassistant.components.aruba
+# homeassistant.components.cisco_ios
+# homeassistant.components.pandora
+# homeassistant.components.unifi_direct
pexpect==4.6.0
# homeassistant.components.rpi_pfio
@@ -822,69 +824,67 @@ pifacecommon==4.2.2
# homeassistant.components.rpi_pfio
pifacedigitalio==3.0.5
-# homeassistant.components.piglow.light
+# homeassistant.components.piglow
piglow==1.2.4
# homeassistant.components.pilight
pilight==0.1.1
-# homeassistant.components.proxy.camera
-# homeassistant.components.qrcode.image_processing
-# homeassistant.components.tensorflow.image_processing
+# homeassistant.components.proxy
+# homeassistant.components.qrcode
+# homeassistant.components.tensorflow
pillow==5.4.1
# homeassistant.components.dominos
pizzapi==0.0.3
-# homeassistant.components.plex.media_player
-# homeassistant.components.plex.sensor
+# homeassistant.components.plex
plexapi==3.0.6
# homeassistant.components.plum_lightpad
plumlightpad==0.0.11
-# homeassistant.components.mhz19.sensor
-# homeassistant.components.serial_pm.sensor
+# homeassistant.components.mhz19
+# homeassistant.components.serial_pm
pmsensor==0.4
-# homeassistant.components.pocketcasts.sensor
+# homeassistant.components.pocketcasts
pocketcasts==0.1
-# homeassistant.components.postnl.sensor
+# homeassistant.components.postnl
postnl_api==1.0.2
-# homeassistant.components.reddit.sensor
+# homeassistant.components.reddit
praw==6.1.1
-# homeassistant.components.islamic_prayer_times.sensor
+# homeassistant.components.islamic_prayer_times
prayer_times_calculator==0.0.3
-# homeassistant.components.prezzibenzina.sensor
+# homeassistant.components.prezzibenzina
prezzibenzina-py==1.1.4
-# homeassistant.components.proliphix.climate
+# homeassistant.components.proliphix
proliphix==0.4.1
# homeassistant.components.prometheus
prometheus_client==0.2.0
-# homeassistant.components.tensorflow.image_processing
+# homeassistant.components.tensorflow
protobuf==3.6.1
-# homeassistant.components.systemmonitor.sensor
+# homeassistant.components.systemmonitor
psutil==5.6.1
# homeassistant.components.wink
pubnubsub-handler==1.0.3
-# homeassistant.components.pushbullet.notify
-# homeassistant.components.pushbullet.sensor
+# homeassistant.components.pushbullet
pushbullet.py==0.11.0
-# homeassistant.components.pushetta.notify
+# homeassistant.components.pushetta
pushetta==1.0.15
-# homeassistant.components.rpi_gpio_pwm.light
+# homeassistant.components.rpi_gpio_pwm
pwmled==1.4.1
# homeassistant.components.august
@@ -893,75 +893,74 @@ py-august==0.7.0
# homeassistant.components.canary
py-canary==0.5.0
-# homeassistant.components.cpuspeed.sensor
+# homeassistant.components.cpuspeed
py-cpuinfo==5.0.0
# homeassistant.components.melissa
py-melissa-climate==2.0.0
-# homeassistant.components.synology.camera
+# homeassistant.components.synology
py-synology==0.2.0
-# homeassistant.components.seventeentrack.sensor
+# homeassistant.components.seventeentrack
py17track==2.2.2
# homeassistant.components.hdmi_cec
pyCEC==0.4.13
# homeassistant.components.tplink
-pyHS100==0.3.4
+pyHS100==0.3.5
-# homeassistant.components.met.weather
-# homeassistant.components.norway_air.air_quality
+# homeassistant.components.met
+# homeassistant.components.norway_air
pyMetno==0.4.6
# homeassistant.components.rfxtrx
pyRFXtrx==0.23
-# homeassistant.components.switchmate.switch
+# homeassistant.components.switchmate
# pySwitchmate==0.4.5
# homeassistant.components.tibber
pyTibber==0.10.1
-# homeassistant.components.dlink.switch
+# homeassistant.components.dlink
pyW215==0.6.0
# homeassistant.components.w800rf32
pyW800rf32==0.1
-# homeassistant.components.noaa_tides.sensor
+# homeassistant.components.noaa_tides
# py_noaa==0.3.0
# homeassistant.components.ads
pyads==3.0.7
-# homeassistant.components.aftership.sensor
+# homeassistant.components.aftership
pyaftership==0.1.2
-# homeassistant.components.airvisual.sensor
+# homeassistant.components.airvisual
pyairvisual==3.0.1
-# homeassistant.components.alarmdotcom.alarm_control_panel
+# homeassistant.components.alarmdotcom
pyalarmdotcom==0.3.2
# homeassistant.components.arlo
pyarlo==0.2.3
# homeassistant.components.netatmo
-pyatmo==1.9
+pyatmo==1.10
# homeassistant.components.apple_tv
pyatv==0.3.12
-# homeassistant.components.bbox.device_tracker
-# homeassistant.components.bbox.sensor
+# homeassistant.components.bbox
pybbox==0.0.5-alpha
-# homeassistant.components.blackbird.media_player
+# homeassistant.components.blackbird
pyblackbird==0.5
-# homeassistant.components.bluetooth_tracker.device_tracker
+# homeassistant.components.bluetooth_tracker
# pybluez==0.22
# homeassistant.components.neato
@@ -973,29 +972,29 @@ pycarwings2==2.8
# homeassistant.components.cloudflare
pycfdns==0.0.1
-# homeassistant.components.channels.media_player
+# homeassistant.components.channels
pychannels==1.0.0
# homeassistant.components.cast
-pychromecast==3.0.0
+pychromecast==3.2.0
-# homeassistant.components.cmus.media_player
+# homeassistant.components.cmus
pycmus==0.1.1
# homeassistant.components.comfoconnect
pycomfoconnect==0.3
-# homeassistant.components.coolmaster.climate
+# homeassistant.components.coolmaster
pycoolmasternet==0.0.4
-# homeassistant.components.microsoft.tts
+# homeassistant.components.microsoft
pycsspeechtts==1.0.2
-# homeassistant.components.cups.sensor
+# homeassistant.components.cups
# pycups==1.9.73
# homeassistant.components.daikin
-pydaikin==1.3.1
+pydaikin==1.4.0
# homeassistant.components.danfoss_air
pydanfossair==0.0.7
@@ -1009,46 +1008,46 @@ pydispatcher==2.0.5
# homeassistant.components.android_ip_webcam
pydroid-ipcam==0.8
-# homeassistant.components.duke_energy.sensor
+# homeassistant.components.duke_energy
pydukeenergy==0.0.6
-# homeassistant.components.ebox.sensor
+# homeassistant.components.ebox
pyebox==1.1.4
-# homeassistant.components.econet.water_heater
+# homeassistant.components.econet
pyeconet==0.0.10
-# homeassistant.components.edimax.switch
+# homeassistant.components.edimax
pyedimax==0.1
# homeassistant.components.eight_sleep
pyeight==0.1.1
-# homeassistant.components.emby.media_player
+# homeassistant.components.emby
pyemby==1.6
# homeassistant.components.envisalink
pyenvisalink==3.8
-# homeassistant.components.ephember.climate
+# homeassistant.components.ephember
pyephember==0.2.0
-# homeassistant.components.everlights.light
+# homeassistant.components.everlights
pyeverlights==0.1.0
-# homeassistant.components.fido.sensor
+# homeassistant.components.fido
pyfido==2.1.1
-# homeassistant.components.flexit.climate
+# homeassistant.components.flexit
pyflexit==0.3
-# homeassistant.components.flic.binary_sensor
+# homeassistant.components.flic
pyflic-homeassistant==0.4.dev0
-# homeassistant.components.flunearyou.sensor
+# homeassistant.components.flunearyou
pyflunearyou==1.0.3
-# homeassistant.components.futurenow.light
+# homeassistant.components.futurenow
pyfnip==0.2
# homeassistant.components.fritzbox
@@ -1057,23 +1056,26 @@ pyfritzhome==0.4.0
# homeassistant.components.ifttt
pyfttt==0.3
-# homeassistant.components.bluetooth_le_tracker.device_tracker
-# homeassistant.components.skybeacon.sensor
-pygatt[GATTTOOL]==3.2.0
+# homeassistant.components.bluetooth_le_tracker
+# homeassistant.components.skybeacon
+pygatt[GATTTOOL]==4.0.1
-# homeassistant.components.gogogate2.cover
+# homeassistant.components.gogogate2
pygogogate2==0.1.1
-# homeassistant.components.gtfs.sensor
+# homeassistant.components.gtfs
pygtfs==0.1.5
-# homeassistant.components.gtt.sensor
+# homeassistant.components.gtt
pygtt==1.1.2
-# homeassistant.components.version.sensor
-pyhaversion==2.0.3
+# homeassistant.components.version
+pyhaversion==2.2.1
-# homeassistant.components.hikvision.binary_sensor
+# homeassistant.components.heos
+pyheos==0.4.0
+
+# homeassistant.components.hikvision
pyhik==0.2.2
# homeassistant.components.hive
@@ -1085,57 +1087,56 @@ pyhomematic==0.1.58
# homeassistant.components.homeworks
pyhomeworks==0.0.6
-# homeassistant.components.hydroquebec.sensor
+# homeassistant.components.hydroquebec
pyhydroquebec==2.2.2
-# homeassistant.components.ialarm.alarm_control_panel
+# homeassistant.components.ialarm
pyialarm==0.3
-# homeassistant.components.icloud.device_tracker
+# homeassistant.components.icloud
pyicloud==0.9.1
-# homeassistant.components.ipma.weather
+# homeassistant.components.ipma
pyipma==1.2.1
-# homeassistant.components.irish_rail_transport.sensor
+# homeassistant.components.irish_rail_transport
pyirishrail==0.0.2
-# homeassistant.components.iss.binary_sensor
+# homeassistant.components.iss
pyiss==1.0.1
-# homeassistant.components.itach.remote
+# homeassistant.components.itach
pyitachip2ir==0.0.7
# homeassistant.components.kira
pykira==0.1.1
-# homeassistant.components.kwb.sensor
+# homeassistant.components.kwb
pykwb==0.0.8
-# homeassistant.components.lacrosse.sensor
+# homeassistant.components.lacrosse
pylacrosse==0.3.1
-# homeassistant.components.lastfm.sensor
+# homeassistant.components.lastfm
pylast==3.1.0
-# homeassistant.components.launch_library.sensor
+# homeassistant.components.launch_library
pylaunches==0.2.0
-# homeassistant.components.lg_netcast.media_player
+# homeassistant.components.lg_netcast
pylgnetcast-homeassistant==0.2.0.dev0
-# homeassistant.components.webostv.media_player
-# homeassistant.components.webostv.notify
+# homeassistant.components.webostv
pylgtv==0.1.9
-# homeassistant.components.linky.sensor
-pylinky==0.3.0
+# homeassistant.components.linky
+pylinky==0.3.3
# homeassistant.components.litejet
pylitejet==0.1
-# homeassistant.components.loopenergy.sensor
-pyloopenergy==0.1.0
+# homeassistant.components.loopenergy
+pyloopenergy==0.1.2
# homeassistant.components.lutron_caseta
pylutron-caseta==0.5.0
@@ -1143,13 +1144,13 @@ pylutron-caseta==0.5.0
# homeassistant.components.lutron
pylutron==0.2.0
-# homeassistant.components.mailgun.notify
+# homeassistant.components.mailgun
pymailgunner==1.4
-# homeassistant.components.mediaroom.media_player
+# homeassistant.components.mediaroom
pymediaroom==0.6.4
-# homeassistant.components.xiaomi_tv.media_player
+# homeassistant.components.xiaomi_tv
pymitv==1.4.3
# homeassistant.components.mochad
@@ -1158,44 +1159,43 @@ pymochad==0.2.0
# homeassistant.components.modbus
pymodbus==1.5.2
-# homeassistant.components.monoprice.media_player
+# homeassistant.components.monoprice
pymonoprice==0.3
-# homeassistant.components.yamaha_musiccast.media_player
+# homeassistant.components.yamaha_musiccast
pymusiccast==0.1.6
-# homeassistant.components.myq.cover
-pymyq==1.1.0
+# homeassistant.components.myq
+pymyq==1.2.0
# homeassistant.components.mysensors
pymysensors==0.18.0
-# homeassistant.components.nanoleaf.light
+# homeassistant.components.nanoleaf
pynanoleaf==0.0.5
-# homeassistant.components.nello.lock
+# homeassistant.components.nello
pynello==2.0.2
-# homeassistant.components.netgear.device_tracker
+# homeassistant.components.netgear
pynetgear==0.5.2
-# homeassistant.components.netio.switch
+# homeassistant.components.netio
pynetio==0.1.9.1
-# homeassistant.components.nuki.lock
+# homeassistant.components.nuki
pynuki==1.3.2
-# homeassistant.components.nut.sensor
+# homeassistant.components.nut
pynut2==2.1.2
-# homeassistant.components.nx584.alarm_control_panel
-# homeassistant.components.nx584.binary_sensor
+# homeassistant.components.nx584
pynx584==0.4
# homeassistant.components.openuv
pyopenuv==1.0.9
-# homeassistant.components.opple.light
+# homeassistant.components.opple
pyoppleio==1.0.5
# homeassistant.components.iota
@@ -1206,68 +1206,67 @@ pyotgw==0.4b3
# homeassistant.auth.mfa_modules.notify
# homeassistant.auth.mfa_modules.totp
-# homeassistant.components.otp.sensor
+# homeassistant.components.otp
pyotp==2.2.6
# homeassistant.components.owlet
pyowlet==1.0.2
-# homeassistant.components.openweathermap.sensor
-# homeassistant.components.openweathermap.weather
+# homeassistant.components.openweathermap
pyowm==2.10.0
# homeassistant.components.lcn
pypck==0.5.9
-# homeassistant.components.pjlink.media_player
+# homeassistant.components.pjlink
pypjlink2==1.2.0
# homeassistant.components.point
pypoint==1.1.1
-# homeassistant.components.pollen.sensor
+# homeassistant.components.pollen
pypollencom==2.2.3
# homeassistant.components.ps4
pyps4-homeassistant==0.5.2
# homeassistant.components.qwikswitch
-pyqwikswitch==0.8
+pyqwikswitch==0.93
-# homeassistant.components.nmbs.sensor
+# homeassistant.components.nmbs
pyrail==0.0.3
# homeassistant.components.rainbird
pyrainbird==0.1.6
-# homeassistant.components.recswitch.switch
+# homeassistant.components.recswitch
pyrecswitch==1.0.2
-# homeassistant.components.ruter.sensor
+# homeassistant.components.ruter
pyruter==1.1.0
# homeassistant.components.sabnzbd
pysabnzbd==1.1.0
-# homeassistant.components.sony_projector.switch
+# homeassistant.components.sony_projector
pysdcp==1
-# homeassistant.components.sensibo.climate
+# homeassistant.components.sensibo
pysensibo==1.0.3
-# homeassistant.components.serial.sensor
+# homeassistant.components.serial
pyserial-asyncio==0.4
-# homeassistant.components.acer_projector.switch
+# homeassistant.components.acer_projector
pyserial==3.1.1
-# homeassistant.components.sesame.lock
+# homeassistant.components.sesame
pysesame==0.1.0
# homeassistant.components.goalfeed
pysher==1.0.1
-# homeassistant.components.sma.sensor
+# homeassistant.components.sma
pysma==0.3.1
# homeassistant.components.smartthings
@@ -1276,40 +1275,43 @@ pysmartapp==0.3.2
# homeassistant.components.smartthings
pysmartthings==0.6.7
-# homeassistant.components.snmp.device_tracker
-# homeassistant.components.snmp.sensor
-# homeassistant.components.snmp.switch
+# homeassistant.components.snmp
pysnmp==4.4.8
# homeassistant.components.sonos
-pysonos==0.0.8
+pysonos==0.0.10
# homeassistant.components.spc
pyspcwebgw==0.4.0
-# homeassistant.components.stride.notify
+# homeassistant.components.stiebel_eltron
+pystiebeleltron==0.0.1.dev2
+
+# homeassistant.components.stride
pystride==0.1.7
-# homeassistant.components.syncthru.sensor
+# homeassistant.components.supla
+pysupla==0.0.3
+
+# homeassistant.components.syncthru
pysyncthru==0.3.1
-# homeassistant.components.tautulli.sensor
+# homeassistant.components.tautulli
pytautulli==0.5.0
-# homeassistant.components.liveboxplaytv.media_player
+# homeassistant.components.liveboxplaytv
pyteleloisirs==3.4
-# homeassistant.components.tfiac.climate
+# homeassistant.components.tfiac
pytfiac==0.3
-# homeassistant.components.thinkingcleaner.sensor
-# homeassistant.components.thinkingcleaner.switch
+# homeassistant.components.thinkingcleaner
pythinkingcleaner==0.0.3
-# homeassistant.components.blockchain.sensor
+# homeassistant.components.blockchain
python-blockchain-api==0.0.2
-# homeassistant.components.clementine.media_player
+# homeassistant.components.clementine
python-clementine-remote==1.0.1
# homeassistant.components.digital_ocean
@@ -1318,30 +1320,28 @@ python-digitalocean==1.13.2
# homeassistant.components.ecobee
python-ecobee-api==0.0.18
-# homeassistant.components.eq3btsmart.climate
+# homeassistant.components.eq3btsmart
# python-eq3bt==0.1.9
-# homeassistant.components.etherscan.sensor
+# homeassistant.components.etherscan
python-etherscan-api==0.0.3
-# homeassistant.components.familyhub.camera
+# homeassistant.components.familyhub
python-family-hub-local==0.0.2
-# homeassistant.components.darksky.sensor
-# homeassistant.components.darksky.weather
+# homeassistant.components.darksky
python-forecastio==1.4.0
# homeassistant.components.gc100
python-gc100==1.0.3a
-# homeassistant.components.gitlab_ci.sensor
+# homeassistant.components.gitlab_ci
python-gitlab==1.6.0
-# homeassistant.components.hp_ilo.sensor
+# homeassistant.components.hp_ilo
python-hpilo==3.9
# homeassistant.components.joaoapps_join
-# homeassistant.components.joaoapps_join.notify
python-join-api==0.0.4
# homeassistant.components.juicenet
@@ -1350,47 +1350,40 @@ python-juicenet==0.0.5
# homeassistant.components.lirc
# python-lirc==1.2.3
-# homeassistant.components.xiaomi_miio.device_tracker
-# homeassistant.components.xiaomi_miio.fan
-# homeassistant.components.xiaomi_miio.light
-# homeassistant.components.xiaomi_miio.remote
-# homeassistant.components.xiaomi_miio.sensor
-# homeassistant.components.xiaomi_miio.switch
-# homeassistant.components.xiaomi_miio.vacuum
+# homeassistant.components.xiaomi_miio
python-miio==0.4.5
-# homeassistant.components.mpd.media_player
+# homeassistant.components.mpd
python-mpd2==1.0.0
-# homeassistant.components.mystrom.light
-# homeassistant.components.mystrom.switch
+# homeassistant.components.mystrom
python-mystrom==0.5.0
# homeassistant.components.nest
python-nest==4.1.0
-# homeassistant.components.nmap_tracker.device_tracker
+# homeassistant.components.nmap_tracker
python-nmap==0.6.1
-# homeassistant.components.pushover.notify
+# homeassistant.components.pushover
python-pushover==0.3
-# homeassistant.components.qbittorrent.sensor
+# homeassistant.components.qbittorrent
python-qbittorrent==0.3.1
-# homeassistant.components.ripple.sensor
+# homeassistant.components.ripple
python-ripple-api==0.0.3
# homeassistant.components.roku
python-roku==3.1.5
-# homeassistant.components.sochain.sensor
+# homeassistant.components.sochain
python-sochain-api==0.0.2
-# homeassistant.components.songpal.media_player
+# homeassistant.components.songpal
python-songpal==0.0.9.1
-# homeassistant.components.synologydsm.sensor
+# homeassistant.components.synologydsm
python-synology==0.2.0
# homeassistant.components.tado
@@ -1399,55 +1392,55 @@ python-tado==0.2.9
# homeassistant.components.telegram_bot
python-telegram-bot==11.1.0
-# homeassistant.components.twitch.sensor
+# homeassistant.components.twitch
python-twitch-client==0.6.0
# homeassistant.components.velbus
-python-velbus==2.0.22
+python-velbus==2.0.24
-# homeassistant.components.vlc.media_player
+# homeassistant.components.vlc
python-vlc==1.1.2
-# homeassistant.components.whois.sensor
+# homeassistant.components.whois
python-whois==0.7.1
# homeassistant.components.wink
python-wink==1.10.3
-# homeassistant.components.awair.sensor
-python_awair==0.0.3
+# homeassistant.components.awair
+python_awair==0.0.4
-# homeassistant.components.swiss_public_transport.sensor
+# homeassistant.components.swiss_public_transport
python_opendata_transport==0.1.4
# homeassistant.components.egardia
pythonegardia==1.0.39
-# homeassistant.components.tile.device_tracker
+# homeassistant.components.tile
pytile==2.0.6
-# homeassistant.components.touchline.climate
+# homeassistant.components.touchline
pytouchline==0.7
-# homeassistant.components.traccar.device_tracker
+# homeassistant.components.traccar
pytraccar==0.5.0
-# homeassistant.components.trackr.device_tracker
+# homeassistant.components.trackr
pytrackr==0.0.5
# homeassistant.components.tradfri
pytradfri[async]==6.0.1
-# homeassistant.components.trafikverket_weatherstation.sensor
+# homeassistant.components.trafikverket_weatherstation
pytrafikverket==0.1.5.9
-# homeassistant.components.ubee.device_tracker
-pyubee==0.2
+# homeassistant.components.ubee
+pyubee==0.5
-# homeassistant.components.unifi.device_tracker
+# homeassistant.components.unifi
pyunifi==2.16
-# homeassistant.components.uptimerobot.binary_sensor
+# homeassistant.components.uptimerobot
pyuptimerobot==0.0.5
# homeassistant.components.keyboard
@@ -1456,52 +1449,52 @@ pyuptimerobot==0.0.5
# homeassistant.components.vera
pyvera==0.2.45
-# homeassistant.components.vesync.switch
+# homeassistant.components.vesync
pyvesync_v2==0.9.6
-# homeassistant.components.vizio.media_player
+# homeassistant.components.vizio
pyvizio==0.0.4
# homeassistant.components.velux
pyvlx==0.2.10
-# homeassistant.components.html5.notify
-pywebpush==1.6.0
+# homeassistant.components.html5
+pywebpush==1.9.2
# homeassistant.components.wemo
pywemo==0.4.34
-# homeassistant.components.xeoma.camera
+# homeassistant.components.xeoma
pyxeoma==1.4.1
# homeassistant.components.zabbix
pyzabbix==0.7.4
-# homeassistant.components.qrcode.image_processing
+# homeassistant.components.qrcode
pyzbar==0.1.7
-# homeassistant.components.qnap.sensor
+# homeassistant.components.qnap
qnapstats==0.2.7
-# homeassistant.components.quantum_gateway.device_tracker
+# homeassistant.components.quantum_gateway
quantum-gateway==0.0.5
# homeassistant.components.rachio
rachiopy==0.1.3
-# homeassistant.components.radiotherm.climate
+# homeassistant.components.radiotherm
radiotherm==2.0.0
# homeassistant.components.raincloud
-raincloudy==0.0.5
+raincloudy==0.0.7
# homeassistant.components.raspihats
# raspihats==2.2.3
-# homeassistant.components.raspyrfm.switch
+# homeassistant.components.raspyrfm
raspyrfm-client==1.2.8
-# homeassistant.components.recollect_waste.sensor
+# homeassistant.components.recollect_waste
recollect-waste==1.0.1
# homeassistant.components.rainmachine
@@ -1519,62 +1512,61 @@ rflink==0.0.37
# homeassistant.components.ring
ring_doorbell==0.2.3
-# homeassistant.components.ritassist.device_tracker
+# homeassistant.components.ritassist
ritassist==0.9.2
-# homeassistant.components.rejseplanen.sensor
+# homeassistant.components.rejseplanen
rjpl==0.3.5
-# homeassistant.components.rocketchat.notify
+# homeassistant.components.rocketchat
rocketchat-API==0.6.1
-# homeassistant.components.roomba.vacuum
+# homeassistant.components.roomba
roombapy==1.3.1
-# homeassistant.components.rova.sensor
+# homeassistant.components.rova
rova==0.1.0
-# homeassistant.components.rpi_rf.switch
+# homeassistant.components.rpi_rf
# rpi-rf==0.9.7
-# homeassistant.components.russound_rnet.media_player
+# homeassistant.components.russound_rnet
russound==0.1.9
-# homeassistant.components.russound_rio.media_player
+# homeassistant.components.russound_rio
russound_rio==0.1.4
-# homeassistant.components.yamaha.media_player
+# homeassistant.components.yamaha
rxv==0.6.0
-# homeassistant.components.samsungtv.media_player
+# homeassistant.components.samsungtv
samsungctl[websocket]==0.7.1
# homeassistant.components.satel_integra
-satel_integra==0.3.2
+satel_integra==0.3.4
-# homeassistant.components.deutsche_bahn.sensor
+# homeassistant.components.deutsche_bahn
schiene==0.23
# homeassistant.components.scsgate
scsgate==0.1.0
-# homeassistant.components.sendgrid.notify
+# homeassistant.components.sendgrid
sendgrid==5.6.0
-# homeassistant.components.sensehat.light
-# homeassistant.components.sensehat.sensor
+# homeassistant.components.sensehat
sense-hat==2.2.0
# homeassistant.components.sense
sense_energy==0.7.0
-# homeassistant.components.aquostv.media_player
+# homeassistant.components.aquostv
sharp_aquos_rc==0.3.2
-# homeassistant.components.shodan.sensor
+# homeassistant.components.shodan
shodan==1.11.1
-# homeassistant.components.simplepush.notify
+# homeassistant.components.simplepush
simplepush==1.1.4
# homeassistant.components.simplisafe
@@ -1584,98 +1576,101 @@ simplisafe-python==3.4.1
sisyphus-control==2.1
# homeassistant.components.skybell
-skybellpy==0.3.0
+skybellpy==0.4.0
-# homeassistant.components.slack.notify
+# homeassistant.components.slack
slacker==0.12.0
# homeassistant.components.sleepiq
sleepyq==0.6
-# homeassistant.components.xmpp.notify
+# homeassistant.components.xmpp
slixmpp==1.4.2
# homeassistant.components.smappee
smappy==0.2.16
+# homeassistant.components.bh1750
+# homeassistant.components.bme280
+# homeassistant.components.bme680
+# homeassistant.components.envirophat
+# homeassistant.components.htu21d
# homeassistant.components.raspihats
-# homeassistant.components.bh1750.sensor
-# homeassistant.components.bme280.sensor
-# homeassistant.components.bme680.sensor
-# homeassistant.components.envirophat.sensor
-# homeassistant.components.htu21d.sensor
# smbus-cffi==0.5.1
# homeassistant.components.smhi
smhi-pkg==1.0.10
-# homeassistant.components.snapcast.media_player
+# homeassistant.components.snapcast
snapcast==2.0.9
-# homeassistant.components.socialblade.sensor
+# homeassistant.components.socialblade
socialbladeclient==0.2
-# homeassistant.components.solaredge.sensor
+# homeassistant.components.solaredge
solaredge==0.0.2
-# homeassistant.components.honeywell.climate
+# homeassistant.components.honeywell
somecomfort==0.5.2
+# homeassistant.components.somfy_mylink
+somfy-mylink-synergy==1.0.4
+
# homeassistant.components.speedtestdotnet
speedtest-cli==2.1.1
# homeassistant.components.spider
spiderpy==1.3.1
-# homeassistant.components.spotcrime.sensor
+# homeassistant.components.spotcrime
spotcrime==1.0.3
-# homeassistant.components.spotify.media_player
+# homeassistant.components.spotify
spotipy-homeassistant==2.4.4.dev1
# homeassistant.components.recorder
-# homeassistant.components.sql.sensor
+# homeassistant.components.sql
sqlalchemy==1.3.0
-# homeassistant.components.srp_energy.sensor
+# homeassistant.components.srp_energy
srpenergy==1.0.6
-# homeassistant.components.starlingbank.sensor
+# homeassistant.components.starlingbank
starlingbank==3.1
# homeassistant.components.statsd
statsd==3.2.1
-# homeassistant.components.steam_online.sensor
+# homeassistant.components.steam_online
steamodd==4.21
-# homeassistant.components.solaredge.sensor
-# homeassistant.components.thermoworks_smoke.sensor
-# homeassistant.components.traccar.device_tracker
+# homeassistant.components.solaredge
+# homeassistant.components.thermoworks_smoke
+# homeassistant.components.traccar
stringcase==1.2.0
# homeassistant.components.ecovacs
sucks==0.9.3
-# homeassistant.components.onvif.camera
+# homeassistant.components.onvif
suds-passworddigest-homeassistant==0.1.2a0.dev0
-# homeassistant.components.onvif.camera
+# homeassistant.components.onvif
suds-py3==1.3.3.0
-# homeassistant.components.swiss_hydrological_data.sensor
+# homeassistant.components.swiss_hydrological_data
swisshydrodata==0.0.3
-# homeassistant.components.synology_srm.device_tracker
+# homeassistant.components.synology_srm
synology-srm==0.0.6
# homeassistant.components.tahoma
tahoma-api==0.0.14
-# homeassistant.components.tank_utility.sensor
+# homeassistant.components.tank_utility
tank_utility==1.4.0
-# homeassistant.components.tapsaff.binary_sensor
+# homeassistant.components.tapsaff
tapsaff==0.2.0
# homeassistant.components.tellstick
@@ -1687,37 +1682,37 @@ tellcore-py==1.1.2
# homeassistant.components.tellduslive
tellduslive==0.10.10
-# homeassistant.components.lg_soundbar.media_player
+# homeassistant.components.lg_soundbar
temescal==0.1
-# homeassistant.components.temper.sensor
+# homeassistant.components.temper
temperusb==1.5.3
# homeassistant.components.tesla
teslajsonpy==0.0.25
-# homeassistant.components.thermoworks_smoke.sensor
+# homeassistant.components.thermoworks_smoke
thermoworks_smoke==0.1.8
# homeassistant.components.thingspeak
thingspeak==0.4.1
-# homeassistant.components.tikteck.light
+# homeassistant.components.tikteck
tikteck==0.4
-# homeassistant.components.todoist.calendar
+# homeassistant.components.todoist
todoist-python==7.0.17
# homeassistant.components.toon
toonapilib==3.2.2
-# homeassistant.components.totalconnect.alarm_control_panel
+# homeassistant.components.totalconnect
total_connect_client==0.25
# homeassistant.components.tplink_lte
tp-connected==0.0.4
-# homeassistant.components.tplink.device_tracker
+# homeassistant.components.tplink
tplink==0.2.1
# homeassistant.components.transmission
@@ -1729,25 +1724,25 @@ tuyapy==0.1.3
# homeassistant.components.twilio
twilio==6.19.1
-# homeassistant.components.uber.sensor
+# homeassistant.components.uber
uber_rides==0.6.0
# homeassistant.components.upcloud
upcloud-api==0.4.3
-# homeassistant.components.ups.sensor
+# homeassistant.components.ups
upsmychoice==1.0.6
-# homeassistant.components.uscis.sensor
+# homeassistant.components.uscis
uscisstatus==0.1.1
-# homeassistant.components.uvc.camera
+# homeassistant.components.uvc
uvcclient==0.11.0
-# homeassistant.components.venstar.climate
+# homeassistant.components.venstar
venstarcolortouch==0.6
-# homeassistant.components.volkszaehler.sensor
+# homeassistant.components.volkszaehler
volkszaehler==0.1.2
# homeassistant.components.volvooncall
@@ -1756,19 +1751,18 @@ volvooncall==0.8.7
# homeassistant.components.verisure
vsure==1.5.2
-# homeassistant.components.vasttrafik.sensor
+# homeassistant.components.vasttrafik
vtjp==0.1.14
# homeassistant.components.vultr
vultr==0.1.2
+# homeassistant.components.panasonic_viera
+# homeassistant.components.samsungtv
# homeassistant.components.wake_on_lan
-# homeassistant.components.panasonic_viera.media_player
-# homeassistant.components.samsungtv.media_player
-# homeassistant.components.wake_on_lan.switch
wakeonlan==1.1.6
-# homeassistant.components.waqi.sensor
+# homeassistant.components.waqi
waqiasync==1.0.0
# homeassistant.components.folder_watcher
@@ -1777,10 +1771,13 @@ watchdog==0.8.3
# homeassistant.components.waterfurnace
waterfurnace==1.1.0
-# homeassistant.components.gpmdp.media_player
+# homeassistant.components.cisco_webex_teams
+webexteamssdk==1.1.1
+
+# homeassistant.components.gpmdp
websocket-client==0.54.0
-# homeassistant.components.webostv.media_player
+# homeassistant.components.webostv
websockets==6.0
# homeassistant.components.wirelesstag
@@ -1792,64 +1789,63 @@ wunderpy2==0.1.6
# homeassistant.components.zigbee
xbee-helper==0.0.7
-# homeassistant.components.xbox_live.sensor
+# homeassistant.components.xbox_live
xboxapi==0.1.1
-# homeassistant.components.xfinity.device_tracker
+# homeassistant.components.xfinity
xfinity-gateway==0.0.4
# homeassistant.components.knx
xknx==0.10.0
-# homeassistant.components.bluesound.media_player
-# homeassistant.components.startca.sensor
-# homeassistant.components.ted5000.sensor
-# homeassistant.components.yr.sensor
-# homeassistant.components.zestimate.sensor
+# homeassistant.components.bluesound
+# homeassistant.components.startca
+# homeassistant.components.ted5000
+# homeassistant.components.yr
+# homeassistant.components.zestimate
xmltodict==0.11.0
# homeassistant.components.xs1
xs1-api-client==2.3.5
-# homeassistant.components.yweather.sensor
-# homeassistant.components.yweather.weather
+# homeassistant.components.yweather
yahooweather==0.10
-# homeassistant.components.yale_smart_alarm.alarm_control_panel
+# homeassistant.components.yale_smart_alarm
yalesmartalarmclient==0.1.6
# homeassistant.components.yeelight
-yeelight==0.4.4
+yeelight==0.5.0
-# homeassistant.components.yeelightsunflower.light
+# homeassistant.components.yeelightsunflower
yeelightsunflower==0.0.10
# homeassistant.components.media_extractor
-youtube_dl==2019.03.18
+youtube_dl==2019.04.07
-# homeassistant.components.zengge.light
+# homeassistant.components.zengge
zengge==0.2
# homeassistant.components.zeroconf
zeroconf==0.21.3
# homeassistant.components.zha
-zha-quirks==0.0.7
+zha-quirks==0.0.8
-# homeassistant.components.zhong_hong.climate
+# homeassistant.components.zhong_hong
zhong_hong_hvac==1.0.9
-# homeassistant.components.ziggo_mediabox_xl.media_player
+# homeassistant.components.ziggo_mediabox_xl
ziggo-mediabox-xl==1.1.0
# homeassistant.components.zha
-zigpy-deconz==0.1.3
+zigpy-deconz==0.1.4
# homeassistant.components.zha
-zigpy-homeassistant==0.3.1
+zigpy-homeassistant==0.3.2
# homeassistant.components.zha
-zigpy-xbee-homeassistant==0.1.3
+zigpy-xbee-homeassistant==0.2.0
# homeassistant.components.zoneminder
zm-py==0.3.3
diff --git a/requirements_docs.txt b/requirements_docs.txt
index eca1abdb365..ce1ea4c5821 100644
--- a/requirements_docs.txt
+++ b/requirements_docs.txt
@@ -1,3 +1,3 @@
-Sphinx==1.8.5
+Sphinx==2.0.1
sphinx-autodoc-typehints==1.6.0
sphinx-autodoc-annotation==1.0.post1
\ No newline at end of file
diff --git a/requirements_test.txt b/requirements_test.txt
index bf96353144c..2fdb8992bfe 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,7 +1,8 @@
# linters such as flake8 and pylint should be pinned, as new releases
# make new things fail. Manually update these pins when pulling in a
# new version
-asynctest==0.12.2
+asynctest==0.12.3
+codecov==2.0.15
coveralls==1.2.0
flake8-docstrings==1.3.0
flake8==3.7.7
@@ -13,5 +14,5 @@ pytest-aiohttp==0.3.0
pytest-cov==2.6.1
pytest-sugar==0.9.2
pytest-timeout==1.3.3
-pytest==4.3.1
+pytest==4.4.0
requests_mock==1.5.2
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 48390ff101a..ee0746f7955 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -2,7 +2,8 @@
# linters such as flake8 and pylint should be pinned, as new releases
# make new things fail. Manually update these pins when pulling in a
# new version
-asynctest==0.12.2
+asynctest==0.12.3
+codecov==2.0.15
coveralls==1.2.0
flake8-docstrings==1.3.0
flake8==3.7.7
@@ -14,30 +15,30 @@ pytest-aiohttp==0.3.0
pytest-cov==2.6.1
pytest-sugar==0.9.2
pytest-timeout==1.3.3
-pytest==4.3.1
+pytest==4.4.0
requests_mock==1.5.2
# homeassistant.components.homekit
-HAP-python==2.4.2
+HAP-python==2.5.0
# homeassistant.components.mobile_app
# homeassistant.components.owntracks
PyNaCl==1.3.0
-# homeassistant.components.rmvtransport.sensor
+# homeassistant.components.rmvtransport
PyRMVtransport==0.1.3
-# homeassistant.components.transport_nsw.sensor
+# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1
-# homeassistant.components.yessssms.notify
+# homeassistant.components.yessssms
YesssSMS==0.2.3
# homeassistant.components.ambient_station
-aioambient==0.2.0
+aioambient==0.3.0
-# homeassistant.components.automatic.device_tracker
+# homeassistant.components.automatic
aioautomatic==0.6.5
# homeassistant.components.aws
@@ -53,62 +54,68 @@ aiohue==1.9.1
# homeassistant.components.unifi
aiounifi==4
-# homeassistant.components.apns.notify
+# homeassistant.components.apns
apns2==0.3.0
# homeassistant.components.stream
av==6.1.2
# homeassistant.components.axis
-axis==17
+axis==22
# homeassistant.components.zha
bellows-homeassistant==0.7.2
-# homeassistant.components.caldav.calendar
-caldav==0.5.0
+# homeassistant.components.caldav
+caldav==0.6.1
-# homeassistant.components.coinmarketcap.sensor
+# homeassistant.components.coinmarketcap
coinmarketcap==5.0.3
# homeassistant.components.ihc
# homeassistant.components.namecheapdns
-# homeassistant.components.ohmconnect.sensor
-# homeassistant.components.upc_connect.device_tracker
+# homeassistant.components.ohmconnect
+# homeassistant.components.upc_connect
defusedxml==0.5.0
-# homeassistant.components.dsmr.sensor
+# homeassistant.components.dsmr
dsmr_parser==0.12
-# homeassistant.components.ee_brightbox.device_tracker
+# homeassistant.components.ee_brightbox
eebrightbox==0.0.4
# homeassistant.components.emulated_roku
emulated_roku==0.1.8
-# homeassistant.components.season.sensor
+# homeassistant.components.season
ephem==3.7.6.0
# homeassistant.components.evohome
-# homeassistant.components.honeywell.climate
-evohomeclient==0.2.8
+# homeassistant.components.honeywell
+evohomeclient==0.3.2
# homeassistant.components.feedreader
feedparser-homeassistant==5.2.2.dev1
-# homeassistant.components.foobot.sensor
+# homeassistant.components.foobot
foobot_async==0.3.1
-# homeassistant.components.google.tts
+# homeassistant.components.google_translate
gTTS-token==1.1.3
-# homeassistant.components.geo_json_events.geo_location
-# homeassistant.components.nsw_rural_fire_service_feed.geo_location
-# homeassistant.components.usgs_earthquakes_feed.geo_location
+# homeassistant.components.geo_json_events
+# homeassistant.components.nsw_rural_fire_service_feed
+# homeassistant.components.usgs_earthquakes_feed
geojson_client==0.3
-# homeassistant.components.geo_rss_events.sensor
-georss_client==0.5
+# homeassistant.components.geo_rss_events
+georss_generic_client==0.2
+
+# homeassistant.components.ign_sismologia
+georss_ign_sismologia_client==0.2
+
+# homeassistant.components.google
+google-api-python-client==1.6.4
# homeassistant.components.ffmpeg
ha-ffmpeg==2.0
@@ -117,37 +124,40 @@ ha-ffmpeg==2.0
hangups==0.4.6
# homeassistant.components.cloud
-hass-nabucasa==0.11
+hass-nabucasa==0.12
-# homeassistant.components.mqtt.server
+# homeassistant.components.mqtt
hbmqtt==0.9.4
-# homeassistant.components.jewish_calendar.sensor
+# homeassistant.components.jewish_calendar
hdate==0.8.7
-# homeassistant.components.workday.binary_sensor
+# homeassistant.components.workday
holidays==0.9.10
# homeassistant.components.frontend
-home-assistant-frontend==20190331.0
+home-assistant-frontend==20190424.0
# homeassistant.components.homekit_controller
homekit[IP]==0.13.0
# homeassistant.components.homematicip_cloud
-homematicip==0.10.6
+homematicip==0.10.7
+
+# homeassistant.components.google
+# homeassistant.components.remember_the_milk
+httplib2==0.10.3
# homeassistant.components.influxdb
-# homeassistant.components.influxdb.sensor
influxdb==5.2.0
# homeassistant.components.verisure
jsonpath==0.75
# homeassistant.components.dyson
-libpurecoollink==0.4.2
+libpurecool==0.5.0
-# homeassistant.components.soundtouch.media_player
+# homeassistant.components.soundtouch
libsoundtouch==0.7.2
# homeassistant.components.luftdaten
@@ -156,47 +166,48 @@ luftdaten==0.3.4
# homeassistant.components.mythicbeastsdns
mbddns==0.1.2
-# homeassistant.components.mfi.sensor
-# homeassistant.components.mfi.switch
+# homeassistant.components.mfi
mficlient==0.3.0
-# homeassistant.components.opencv.image_processing
-# homeassistant.components.pollen.sensor
-# homeassistant.components.tensorflow.image_processing
-# homeassistant.components.trend.binary_sensor
+# homeassistant.components.opencv
+# homeassistant.components.pollen
+# homeassistant.components.tensorflow
+# homeassistant.components.trend
numpy==1.16.2
+# homeassistant.components.google
+oauth2client==4.0.0
+
# homeassistant.components.mqtt
# homeassistant.components.shiftr
paho-mqtt==1.4.0
-# homeassistant.components.aruba.device_tracker
-# homeassistant.components.cisco_ios.device_tracker
-# homeassistant.components.pandora.media_player
-# homeassistant.components.unifi_direct.device_tracker
+# homeassistant.components.aruba
+# homeassistant.components.cisco_ios
+# homeassistant.components.pandora
+# homeassistant.components.unifi_direct
pexpect==4.6.0
# homeassistant.components.pilight
pilight==0.1.1
-# homeassistant.components.mhz19.sensor
-# homeassistant.components.serial_pm.sensor
+# homeassistant.components.mhz19
+# homeassistant.components.serial_pm
pmsensor==0.4
# homeassistant.components.prometheus
prometheus_client==0.2.0
-# homeassistant.components.pushbullet.notify
-# homeassistant.components.pushbullet.sensor
+# homeassistant.components.pushbullet
pushbullet.py==0.11.0
# homeassistant.components.canary
py-canary==0.5.0
# homeassistant.components.tplink
-pyHS100==0.3.4
+pyHS100==0.3.5
-# homeassistant.components.blackbird.media_player
+# homeassistant.components.blackbird
pyblackbird==0.5
# homeassistant.components.deconz
@@ -205,17 +216,19 @@ pydeconz==54
# homeassistant.components.zwave
pydispatcher==2.0.5
+# homeassistant.components.heos
+pyheos==0.4.0
+
# homeassistant.components.homematic
pyhomematic==0.1.58
# homeassistant.components.litejet
pylitejet==0.1
-# homeassistant.components.monoprice.media_player
+# homeassistant.components.monoprice
pymonoprice==0.3
-# homeassistant.components.nx584.alarm_control_panel
-# homeassistant.components.nx584.binary_sensor
+# homeassistant.components.nx584
pynx584==0.4
# homeassistant.components.openuv
@@ -223,14 +236,14 @@ pyopenuv==1.0.9
# homeassistant.auth.mfa_modules.notify
# homeassistant.auth.mfa_modules.totp
-# homeassistant.components.otp.sensor
+# homeassistant.components.otp
pyotp==2.2.6
# homeassistant.components.ps4
pyps4-homeassistant==0.5.2
# homeassistant.components.qwikswitch
-pyqwikswitch==0.8
+pyqwikswitch==0.93
# homeassistant.components.smartthings
pysmartapp==0.3.2
@@ -239,29 +252,28 @@ pysmartapp==0.3.2
pysmartthings==0.6.7
# homeassistant.components.sonos
-pysonos==0.0.8
+pysonos==0.0.10
# homeassistant.components.spc
pyspcwebgw==0.4.0
-# homeassistant.components.darksky.sensor
-# homeassistant.components.darksky.weather
+# homeassistant.components.darksky
python-forecastio==1.4.0
# homeassistant.components.nest
python-nest==4.1.0
-# homeassistant.components.awair.sensor
-python_awair==0.0.3
+# homeassistant.components.awair
+python_awair==0.0.4
# homeassistant.components.tradfri
pytradfri[async]==6.0.1
-# homeassistant.components.unifi.device_tracker
+# homeassistant.components.unifi
pyunifi==2.16
-# homeassistant.components.html5.notify
-pywebpush==1.6.0
+# homeassistant.components.html5
+pywebpush==1.9.2
# homeassistant.components.rainmachine
regenmaschine==1.4.0
@@ -275,7 +287,7 @@ rflink==0.0.37
# homeassistant.components.ring
ring_doorbell==0.2.3
-# homeassistant.components.yamaha.media_player
+# homeassistant.components.yamaha
rxv==0.6.0
# homeassistant.components.simplisafe
@@ -287,14 +299,14 @@ sleepyq==0.6
# homeassistant.components.smhi
smhi-pkg==1.0.10
-# homeassistant.components.honeywell.climate
+# homeassistant.components.honeywell
somecomfort==0.5.2
# homeassistant.components.recorder
-# homeassistant.components.sql.sensor
+# homeassistant.components.sql
sqlalchemy==1.3.0
-# homeassistant.components.srp_energy.sensor
+# homeassistant.components.srp_energy
srpenergy==1.0.6
# homeassistant.components.statsd
@@ -303,7 +315,7 @@ statsd==3.2.1
# homeassistant.components.toon
toonapilib==3.2.2
-# homeassistant.components.uvc.camera
+# homeassistant.components.uvc
uvcclient==0.11.0
# homeassistant.components.verisure
@@ -312,11 +324,10 @@ vsure==1.5.2
# homeassistant.components.vultr
vultr==0.1.2
+# homeassistant.components.panasonic_viera
+# homeassistant.components.samsungtv
# homeassistant.components.wake_on_lan
-# homeassistant.components.panasonic_viera.media_player
-# homeassistant.components.samsungtv.media_player
-# homeassistant.components.wake_on_lan.switch
wakeonlan==1.1.6
# homeassistant.components.zha
-zigpy-homeassistant==0.3.1
+zigpy-homeassistant==0.3.2
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index d2d6588672f..f71b8944d7c 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -1,11 +1,14 @@
#!/usr/bin/env python3
"""Generate an updated requirements_all.txt."""
+import fnmatch
import importlib
import os
+import pathlib
import pkgutil
import re
import sys
-import fnmatch
+
+from script.hassfest.model import Integration
COMMENT_REQUIREMENTS = (
'Adafruit-DHT',
@@ -59,7 +62,9 @@ TEST_REQUIREMENTS = (
'feedparser-homeassistant',
'foobot_async',
'geojson_client',
- 'georss_client',
+ 'georss_generic_client',
+ 'georss_ign_sismologia_client',
+ 'google-api-python-client',
'gTTS-token',
'ha-ffmpeg',
'hangups',
@@ -72,14 +77,16 @@ TEST_REQUIREMENTS = (
'home-assistant-frontend',
'homekit[IP]',
'homematicip',
+ 'httplib2',
'influxdb',
'jsonpath',
- 'libpurecoollink',
+ 'libpurecool',
'libsoundtouch',
'luftdaten',
'mbddns',
'mficlient',
'numpy',
+ 'oauth2client',
'paho-mqtt',
'pexpect',
'pilight',
@@ -90,6 +97,7 @@ TEST_REQUIREMENTS = (
'pyblackbird',
'pydeconz',
'pydispatcher',
+ 'pyheos',
'pyhomematic',
'pylitejet',
'pymonoprice',
@@ -212,36 +220,8 @@ def gather_modules():
errors = []
- for package in sorted(
- explore_module('homeassistant.components', True) +
- explore_module('homeassistant.scripts', True) +
- explore_module('homeassistant.auth', True)):
- try:
- module = importlib.import_module(package)
- except ImportError as err:
- for pattern in IGNORE_PACKAGES:
- if fnmatch.fnmatch(package, pattern):
- break
- else:
- print("{}: {}".format(package.replace('.', '/') + '.py', err))
- errors.append(package)
- continue
-
- if not getattr(module, 'REQUIREMENTS', None):
- continue
-
- for req in module.REQUIREMENTS:
- if req in IGNORE_REQ:
- continue
- if '://' in req and 'pyharmony' not in req:
- errors.append(
- "{}[Only pypi dependencies are allowed: {}]".format(
- package, req))
- if req.partition('==')[1] == '' and req not in IGNORE_PIN:
- errors.append(
- "{}[Please pin requirement {}, see {}]".format(
- package, req, URL_PIN))
- reqs.setdefault(req, []).append(package)
+ gather_requirements_from_manifests(errors, reqs)
+ gather_requirements_from_modules(errors, reqs)
for key in reqs:
reqs[key] = sorted(reqs[key],
@@ -256,12 +236,69 @@ def gather_modules():
return reqs
+def gather_requirements_from_manifests(errors, reqs):
+ """Gather all of the requirements from manifests."""
+ integrations = Integration.load_dir(pathlib.Path(
+ 'homeassistant/components'
+ ))
+ for domain in sorted(integrations):
+ integration = integrations[domain]
+
+ if not integration.manifest:
+ errors.append(
+ 'The manifest for component {} is invalid.'.format(domain)
+ )
+ continue
+
+ process_requirements(
+ errors,
+ integration.manifest['requirements'],
+ 'homeassistant.components.{}'.format(domain),
+ reqs
+ )
+
+
+def gather_requirements_from_modules(errors, reqs):
+ """Collect the requirements from the modules directly."""
+ for package in sorted(
+ explore_module('homeassistant.scripts', True) +
+ explore_module('homeassistant.auth', True)):
+ try:
+ module = importlib.import_module(package)
+ except ImportError as err:
+ for pattern in IGNORE_PACKAGES:
+ if fnmatch.fnmatch(package, pattern):
+ break
+ else:
+ print("{}: {}".format(package.replace('.', '/') + '.py', err))
+ errors.append(package)
+ continue
+
+ if getattr(module, 'REQUIREMENTS', None):
+ process_requirements(errors, module.REQUIREMENTS, package, reqs)
+
+
+def process_requirements(errors, module_requirements, package, reqs):
+ """Process all of the requirements."""
+ for req in module_requirements:
+ if req in IGNORE_REQ:
+ continue
+ if '://' in req:
+ errors.append(
+ "{}[Only pypi dependencies are allowed: {}]".format(
+ package, req))
+ if req.partition('==')[1] == '' and req not in IGNORE_PIN:
+ errors.append(
+ "{}[Please pin requirement {}, see {}]".format(
+ package, req, URL_PIN))
+ reqs.setdefault(req, []).append(package)
+
+
def generate_requirements_list(reqs):
"""Generate a pip file based on requirements."""
output = []
for pkg, requirements in sorted(reqs.items(), key=lambda item: item[0]):
- for req in sorted(requirements,
- key=lambda name: (len(name.split('.')), name)):
+ for req in sorted(requirements):
output.append('\n# {}'.format(req))
if comment_requirement(pkg):
diff --git a/script/hassfest/__init__.py b/script/hassfest/__init__.py
new file mode 100644
index 00000000000..2fa7997162f
--- /dev/null
+++ b/script/hassfest/__init__.py
@@ -0,0 +1 @@
+"""Manifest validator."""
diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py
new file mode 100644
index 00000000000..b555f98d883
--- /dev/null
+++ b/script/hassfest/__main__.py
@@ -0,0 +1,86 @@
+"""Validate manifests."""
+import pathlib
+import sys
+
+from .model import Integration, Config
+from . import dependencies, manifest, codeowners, services
+
+PLUGINS = [
+ manifest,
+ dependencies,
+ codeowners,
+ services,
+]
+
+
+def get_config() -> Config:
+ """Return config."""
+ if not pathlib.Path('requirements_all.txt').is_file():
+ raise RuntimeError("Run from project root")
+
+ return Config(
+ root=pathlib.Path('.').absolute(),
+ action='validate' if sys.argv[-1] == 'validate' else 'generate',
+ )
+
+
+def main():
+ """Validate manifests."""
+ try:
+ config = get_config()
+ except RuntimeError as err:
+ print(err)
+ return 1
+
+ integrations = Integration.load_dir(
+ pathlib.Path('homeassistant/components')
+ )
+ manifest.validate(integrations, config)
+ dependencies.validate(integrations, config)
+ codeowners.validate(integrations, config)
+ services.validate(integrations, config)
+
+ # When we generate, all errors that are fixable will be ignored,
+ # as generating them will be fixed.
+ if config.action == 'generate':
+ general_errors = [err for err in config.errors if not err.fixable]
+ invalid_itg = [
+ itg for itg in integrations.values()
+ if any(
+ not error.fixable for error in itg.errors
+ )
+ ]
+ else:
+ # action == validate
+ general_errors = config.errors
+ invalid_itg = [itg for itg in integrations.values() if itg.errors]
+
+ print("Integrations:", len(integrations))
+ print("Invalid integrations:", len(invalid_itg))
+
+ if not invalid_itg and not general_errors:
+ codeowners.generate(integrations, config)
+ return 0
+
+ print()
+ if config.action == 'generate':
+ print("Found errors. Generating files canceled.")
+ print()
+
+ if general_errors:
+ print("General errors:")
+ for error in general_errors:
+ print("*", error)
+ print()
+
+ for integration in sorted(invalid_itg, key=lambda itg: itg.domain):
+ print("Integration {}:".format(integration.domain))
+ for error in integration.errors:
+ print("*", error)
+ print()
+
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py
new file mode 100755
index 00000000000..8ba2008f1cd
--- /dev/null
+++ b/script/hassfest/codeowners.py
@@ -0,0 +1,85 @@
+"""Generate CODEOWNERS."""
+from typing import Dict
+
+from .model import Integration, Config
+
+BASE = """
+# This file is generated by script/manifest/codeowners.py
+# People marked here will be automatically requested for a review
+# when the code that they own is touched.
+# https://github.com/blog/2392-introducing-code-owners
+
+# Home Assistant Core
+setup.py @home-assistant/core
+homeassistant/*.py @home-assistant/core
+homeassistant/helpers/* @home-assistant/core
+homeassistant/util/* @home-assistant/core
+
+# Virtualization
+Dockerfile @home-assistant/docker
+virtualization/Docker/* @home-assistant/docker
+
+# Other code
+homeassistant/scripts/check_config.py @kellerza
+
+# Integrations
+""".strip()
+
+INDIVIDUAL_FILES = """
+# Individual files
+homeassistant/components/group/cover @cdce8p
+homeassistant/components/demo/weather @fabaff
+"""
+
+
+def generate_and_validate(integrations: Dict[str, Integration]):
+ """Generate CODEOWNERS."""
+ parts = [BASE]
+
+ for domain in sorted(integrations):
+ integration = integrations[domain]
+
+ if not integration.manifest:
+ continue
+
+ codeowners = integration.manifest['codeowners']
+
+ if not codeowners:
+ continue
+
+ for owner in codeowners:
+ if not owner.startswith('@'):
+ integration.add_error(
+ 'codeowners',
+ 'Code owners need to be valid GitHub handles.',
+ )
+
+ parts.append("homeassistant/components/{}/* {}".format(
+ domain, ' '.join(codeowners)))
+
+ parts.append('\n' + INDIVIDUAL_FILES.strip())
+
+ return '\n'.join(parts)
+
+
+def validate(integrations: Dict[str, Integration], config: Config):
+ """Validate CODEOWNERS."""
+ codeowners_path = config.root / 'CODEOWNERS'
+ config.cache['codeowners'] = content = generate_and_validate(integrations)
+
+ with open(str(codeowners_path), 'r') as fp:
+ if fp.read().strip() != content:
+ config.add_error(
+ "codeowners",
+ "File CODEOWNERS is not up to date. "
+ "Run python3 -m script.hassfest",
+ fixable=True
+ )
+ return
+
+
+def generate(integrations: Dict[str, Integration], config: Config):
+ """Generate CODEOWNERS."""
+ codeowners_path = config.root / 'CODEOWNERS'
+ with open(str(codeowners_path), 'w') as fp:
+ fp.write(config.cache['codeowners'] + '\n')
diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py
new file mode 100644
index 00000000000..25553be1124
--- /dev/null
+++ b/script/hassfest/dependencies.py
@@ -0,0 +1,72 @@
+"""Validate dependencies."""
+import pathlib
+import re
+from typing import Set, Dict
+
+from .model import Integration
+
+
+def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) \
+ -> Set[str]:
+ """Recursively go through a dir and it's children and find the regex."""
+ pattern = re.compile(search_pattern)
+ found = set()
+
+ for fil in path.glob(glob_pattern):
+ if not fil.is_file():
+ continue
+
+ for match in pattern.finditer(fil.read_text()):
+ found.add(match.groups()[0])
+
+ return found
+
+
+ALLOWED_USED_COMPONENTS = {
+ # This component will always be set up
+ 'persistent_notification',
+ # These allow to register things without being set up
+ 'conversation',
+ 'frontend',
+ 'hassio',
+ 'system_health',
+ 'websocket_api',
+}
+
+
+def validate_dependencies(integration: Integration):
+ """Validate all dependencies."""
+ # Find usage of hass.components
+ referenced = grep_dir(integration.path, "**/*.py",
+ r"hass\.components\.(\w+)")
+ referenced -= ALLOWED_USED_COMPONENTS
+ referenced -= set(integration.manifest['dependencies'])
+ referenced -= set(integration.manifest.get('after_dependencies', []))
+
+ if referenced:
+ for domain in sorted(referenced):
+ print("Warning: {} references integration {} but it's not a "
+ "dependency".format(integration.domain, domain))
+ # Not enforced yet.
+ # integration.add_error(
+ # 'dependencies',
+ # "Using component {} but it's not a dependency".format(domain)
+ # )
+
+
+def validate(integrations: Dict[str, Integration], config):
+ """Handle dependencies for integrations."""
+ # check for non-existing dependencies
+ for integration in integrations.values():
+ if not integration.manifest:
+ continue
+
+ validate_dependencies(integration)
+
+ # check that all referenced dependencies exist
+ for dep in integration.manifest['dependencies']:
+ if dep not in integrations:
+ integration.add_error(
+ 'dependencies',
+ "Dependency {} does not exist"
+ )
diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py
new file mode 100644
index 00000000000..30f89231299
--- /dev/null
+++ b/script/hassfest/manifest.py
@@ -0,0 +1,41 @@
+"""Manifest validation."""
+from typing import Dict
+
+import voluptuous as vol
+from voluptuous.humanize import humanize_error
+
+from .model import Integration
+
+
+MANIFEST_SCHEMA = vol.Schema({
+ vol.Required('domain'): str,
+ vol.Required('name'): str,
+ vol.Required('documentation'): str,
+ vol.Required('requirements'): [str],
+ vol.Required('dependencies'): [str],
+ vol.Optional('after_dependencies'): [str],
+ vol.Required('codeowners'): [str],
+})
+
+
+def validate_manifest(integration: Integration):
+ """Validate manifest."""
+ try:
+ MANIFEST_SCHEMA(integration.manifest)
+ except vol.Invalid as err:
+ integration.add_error(
+ 'manifest',
+ "Invalid manifest: {}".format(
+ humanize_error(integration.manifest, err)))
+ integration.manifest = None
+ return
+
+ if integration.manifest['domain'] != integration.path.name:
+ integration.add_error('manifest', 'Domain does not match dir name')
+
+
+def validate(integrations: Dict[str, Integration], config):
+ """Handle all integrations manifests."""
+ for integration in integrations.values():
+ if integration.manifest:
+ validate_manifest(integration)
diff --git a/script/hassfest/manifest_helper.py b/script/hassfest/manifest_helper.py
new file mode 100644
index 00000000000..3b4cfa11796
--- /dev/null
+++ b/script/hassfest/manifest_helper.py
@@ -0,0 +1,15 @@
+"""Helpers to deal with manifests."""
+import json
+import pathlib
+
+
+component_dir = pathlib.Path('homeassistant/components')
+
+
+def iter_manifests():
+ """Iterate over all available manifests."""
+ manifests = [
+ json.loads(fil.read_text())
+ for fil in component_dir.glob('*/manifest.json')
+ ]
+ return sorted(manifests, key=lambda man: man['domain'])
diff --git a/script/hassfest/model.py b/script/hassfest/model.py
new file mode 100644
index 00000000000..059231cf954
--- /dev/null
+++ b/script/hassfest/model.py
@@ -0,0 +1,87 @@
+"""Models for manifest validator."""
+import json
+from typing import List, Dict, Any
+import pathlib
+
+import attr
+
+
+@attr.s
+class Error:
+ """Error validating an integration."""
+
+ plugin = attr.ib(type=str)
+ error = attr.ib(type=str)
+ fixable = attr.ib(type=bool, default=False)
+
+ def __str__(self) -> str:
+ """Represent error as string."""
+ return "[{}] {}".format(self.plugin.upper(), self.error)
+
+
+@attr.s
+class Config:
+ """Config for the run."""
+
+ root = attr.ib(type=pathlib.Path)
+ action = attr.ib(type=str)
+ errors = attr.ib(type=List[Error], factory=list)
+ cache = attr.ib(type=Dict[str, Any], factory=dict)
+
+ def add_error(self, *args, **kwargs):
+ """Add an error."""
+ self.errors.append(Error(*args, **kwargs))
+
+
+@attr.s
+class Integration:
+ """Represent an integration in our validator."""
+
+ @classmethod
+ def load_dir(cls, path: pathlib.Path):
+ """Load all integrations in a directory."""
+ assert path.is_dir()
+ integrations = {}
+ for fil in path.iterdir():
+ if fil.is_file() or fil.name == '__pycache__':
+ continue
+
+ integration = cls(fil)
+ integration.load_manifest()
+ integrations[integration.domain] = integration
+
+ return integrations
+
+ path = attr.ib(type=pathlib.Path)
+ manifest = attr.ib(type=dict, default=None)
+ errors = attr.ib(type=List[Error], factory=list)
+
+ @property
+ def domain(self) -> str:
+ """Integration domain."""
+ return self.path.name
+
+ def add_error(self, *args, **kwargs):
+ """Add an error."""
+ self.errors.append(Error(*args, **kwargs))
+
+ def load_manifest(self) -> None:
+ """Load manifest."""
+ manifest_path = self.path / 'manifest.json'
+ if not manifest_path.is_file():
+ self.add_error(
+ 'model',
+ "Manifest file {} not found".format(manifest_path)
+ )
+ return
+
+ try:
+ manifest = json.loads(manifest_path.read_text())
+ except ValueError as err:
+ self.add_error(
+ 'model',
+ "Manifest contains invalid JSON: {}".format(err)
+ )
+ return
+
+ self.manifest = manifest
diff --git a/script/hassfest/services.py b/script/hassfest/services.py
new file mode 100644
index 00000000000..bb04d2fc13f
--- /dev/null
+++ b/script/hassfest/services.py
@@ -0,0 +1,101 @@
+"""Validate dependencies."""
+import pathlib
+from typing import Dict
+
+import re
+import voluptuous as vol
+from voluptuous.humanize import humanize_error
+
+from homeassistant.exceptions import HomeAssistantError
+from homeassistant.helpers import config_validation as cv
+from homeassistant.util.yaml import load_yaml
+
+from .model import Integration
+
+
+def exists(value):
+ """Check if value exists."""
+ if value is None:
+ raise vol.Invalid("Value cannot be None")
+ return value
+
+
+FIELD_SCHEMA = vol.Schema({
+ vol.Required('description'): str,
+ vol.Optional('example'): exists,
+ vol.Optional('default'): exists,
+ vol.Optional('values'): exists,
+ vol.Optional('required'): bool,
+})
+
+SERVICE_SCHEMA = vol.Schema({
+ vol.Required('description'): str,
+ vol.Optional('fields'): vol.Schema({
+ str: FIELD_SCHEMA
+ })
+})
+
+SERVICES_SCHEMA = vol.Schema({
+ cv.slug: SERVICE_SCHEMA
+})
+
+
+def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) \
+ -> bool:
+ """Recursively go through a dir and it's children and find the regex."""
+ pattern = re.compile(search_pattern)
+
+ for fil in path.glob(glob_pattern):
+ if not fil.is_file():
+ continue
+
+ if pattern.search(fil.read_text()):
+ return True
+
+ return False
+
+
+def validate_services(integration: Integration):
+ """Validate services."""
+ # Find if integration uses services
+ has_services = grep_dir(integration.path, "**/*.py",
+ r"hass\.(services|async_register)")
+
+ if not has_services:
+ return
+
+ try:
+ data = load_yaml(str(integration.path / 'services.yaml'))
+ except FileNotFoundError:
+ integration.add_error(
+ 'services', 'Registers services but has no services.yaml')
+ return
+ except HomeAssistantError:
+ integration.add_error(
+ 'services', 'Registers services but unable to load services.yaml')
+ return
+
+ try:
+ SERVICES_SCHEMA(data)
+ except vol.Invalid as err:
+ integration.add_error(
+ 'services',
+ "Invalid services.yaml: {}".format(humanize_error(data, err)))
+
+
+def validate(integrations: Dict[str, Integration], config):
+ """Handle dependencies for integrations."""
+ # check services.yaml is cool
+ for integration in integrations.values():
+ if not integration.manifest:
+ continue
+
+ validate_services(integration)
+
+ # check that all referenced dependencies exist
+ for dep in integration.manifest['dependencies']:
+ if dep not in integrations:
+ integration.add_error(
+ 'dependencies',
+ "Dependency {} does not exist"
+ )
diff --git a/script/inspect_schemas.py b/script/inspect_schemas.py
index 46d5cf92ecc..9904552c681 100755
--- a/script/inspect_schemas.py
+++ b/script/inspect_schemas.py
@@ -48,7 +48,7 @@ def main():
schema_type, schema = _identify_config_schema(module)
- add_msg("CONFIG_SCHEMA " + schema_type, module_name + ' ' +
+ add_msg("CONFIG_SCHEMA " + str(schema_type), module_name + ' ' +
color('cyan', str(schema)[:60]))
for key in sorted(msg):
diff --git a/setup.py b/setup.py
index 8f7e84dd8d8..6f67f93d3e2 100755
--- a/setup.py
+++ b/setup.py
@@ -39,15 +39,15 @@ REQUIRES = [
'bcrypt==3.1.6',
'certifi>=2018.04.16',
'jinja2>=2.10',
- 'PyJWT==1.6.4',
+ 'PyJWT==1.7.1',
# PyJWT has loose dependency. We want the latest one.
- 'cryptography==2.5',
+ 'cryptography==2.6.1',
'pip>=8.0.3',
- 'python-slugify==1.2.6',
- 'pytz>=2018.07',
+ 'python-slugify==3.0.2',
+ 'pytz>=2019.01',
'pyyaml>=3.13,<4',
'requests==2.21.0',
- 'ruamel.yaml==0.15.89',
+ 'ruamel.yaml==0.15.91',
'voluptuous==0.11.5',
'voluptuous-serialize==2.1.0',
]
diff --git a/tests/common.py b/tests/common.py
index 9fe5375ad7c..2467dae04b9 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -4,6 +4,7 @@ import functools as ft
import json
import logging
import os
+import uuid
import sys
import threading
@@ -16,7 +17,7 @@ from unittest.mock import MagicMock, Mock, patch
import homeassistant.util.dt as date_util
import homeassistant.util.yaml as yaml
-from homeassistant import auth, config_entries, core as ha
+from homeassistant import auth, config_entries, core as ha, loader
from homeassistant.auth import (
models as auth_models, auth_store, providers as auth_providers,
permissions as auth_permissions)
@@ -35,6 +36,7 @@ from homeassistant.util.unit_system import METRIC_SYSTEM
from homeassistant.util.async_ import (
run_callback_threadsafe, run_coroutine_threadsafe)
+
_TEST_INSTANCE_PORT = SERVER_PORT
_LOGGER = logging.getLogger(__name__)
INSTANCES = []
@@ -244,7 +246,7 @@ def async_fire_mqtt_message(hass, topic, payload, qos=0, retain=False):
if isinstance(payload, str):
payload = payload.encode('utf-8')
msg = mqtt.Message(topic, payload, qos, retain)
- hass.async_run_job(hass.data['mqtt']._mqtt_on_message, None, None, msg)
+ hass.data['mqtt']._mqtt_handle_message(msg)
fire_mqtt_message = threadsafe_callback_factory(async_fire_mqtt_message)
@@ -287,8 +289,7 @@ def mock_state_change_event(hass, new_state, old_state=None):
hass.bus.fire(EVENT_STATE_CHANGED, event_data, context=new_state.context)
-@asyncio.coroutine
-def async_mock_mqtt_component(hass, config=None):
+async def async_mock_mqtt_component(hass, config=None):
"""Mock the MQTT component."""
if config is None:
config = {mqtt.CONF_BROKER: 'mock-broker'}
@@ -299,10 +300,11 @@ def async_mock_mqtt_component(hass, config=None):
mock_client().unsubscribe.return_value = (0, 0)
mock_client().publish.return_value = (0, 0)
- result = yield from async_setup_component(hass, mqtt.DOMAIN, {
+ result = await async_setup_component(hass, mqtt.DOMAIN, {
mqtt.DOMAIN: config
})
assert result
+ await hass.async_block_till_done()
hass.data['mqtt'] = MagicMock(spec_set=hass.data['mqtt'],
wraps=hass.data['mqtt'])
@@ -440,12 +442,16 @@ class MockModule:
requirements=None, config_schema=None, platform_schema=None,
platform_schema_base=None, async_setup=None,
async_setup_entry=None, async_unload_entry=None,
- async_migrate_entry=None, async_remove_entry=None):
+ async_migrate_entry=None, async_remove_entry=None,
+ partial_manifest=None):
"""Initialize the mock module."""
self.__name__ = 'homeassistant.components.{}'.format(domain)
+ self.__file__ = 'homeassistant/components/{}'.format(domain)
self.DOMAIN = domain
self.DEPENDENCIES = dependencies or []
self.REQUIREMENTS = requirements or []
+ # Overlay to be used when generating manifest from this module
+ self._partial_manifest = partial_manifest
if config_schema is not None:
self.CONFIG_SCHEMA = config_schema
@@ -478,11 +484,19 @@ class MockModule:
if async_remove_entry is not None:
self.async_remove_entry = async_remove_entry
+ def mock_manifest(self):
+ """Generate a mock manifest to represent this module."""
+ return {
+ **loader.manifest_from_legacy_module(self.DOMAIN, self),
+ **(self._partial_manifest or {})
+ }
+
class MockPlatform:
"""Provide a fake platform."""
- __name__ = "homeassistant.components.light.bla"
+ __name__ = 'homeassistant.components.light.bla'
+ __file__ = 'homeassistant/components/blah/light'
# pylint: disable=invalid-name
def __init__(self, setup_platform=None, dependencies=None,
@@ -604,7 +618,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
connection_class=config_entries.CONN_CLASS_UNKNOWN):
"""Initialize a mock config entry."""
kwargs = {
- 'entry_id': entry_id or 'mock-id',
+ 'entry_id': entry_id or uuid.uuid4().hex,
'domain': domain,
'data': data or {},
'options': options,
@@ -692,14 +706,16 @@ def assert_setup_component(count, domain=None):
"""
config = {}
- @ha.callback
- def mock_psc(hass, config_input, domain):
+ async def mock_psc(hass, config_input, integration):
"""Mock the prepare_setup_component to capture config."""
- res = async_process_component_config(
- hass, config_input, domain)
- config[domain] = None if res is None else res.get(domain)
+ domain_input = integration.domain
+ res = await async_process_component_config(
+ hass, config_input, integration)
+ config[domain_input] = None if res is None else res.get(domain_input)
_LOGGER.debug("Configuration for %s, Validated: %s, Original %s",
- domain, config[domain], config_input.get(domain))
+ domain_input,
+ config[domain_input],
+ config_input.get(domain_input))
return res
assert isinstance(config, dict)
@@ -894,3 +910,33 @@ async def flush_store(store):
async def get_system_health_info(hass, domain):
"""Get system health info."""
return await hass.data['system_health']['info'][domain](hass)
+
+
+def mock_integration(hass, module):
+ """Mock an integration."""
+ integration = loader.Integration(
+ hass, 'homeassisant.components.{}'.format(module.DOMAIN), None,
+ module.mock_manifest())
+
+ _LOGGER.info("Adding mock integration: %s", module.DOMAIN)
+ hass.data.setdefault(
+ loader.DATA_INTEGRATIONS, {}
+ )[module.DOMAIN] = integration
+ hass.data.setdefault(loader.DATA_COMPONENTS, {})[module.DOMAIN] = module
+
+
+def mock_entity_platform(hass, platform_path, module):
+ """Mock a entity platform.
+
+ platform_path is in form light.hue. Will create platform
+ hue.light.
+ """
+ domain, platform_name = platform_path.split('.')
+ integration_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {})
+ module_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {})
+
+ if platform_name not in integration_cache:
+ mock_integration(hass, MockModule(platform_name))
+
+ _LOGGER.info("Adding mock integration platform: %s", platform_path)
+ module_cache["{}.{}".format(platform_name, domain)] = module
diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py
index 803a15e9634..86a1a3daff5 100644
--- a/tests/components/automation/test_numeric_state.py
+++ b/tests/components/automation/test_numeric_state.py
@@ -703,7 +703,7 @@ async def test_if_action(hass, calls):
async def test_if_fails_setup_bad_for(hass, calls):
"""Test for setup failure for bad for."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
@@ -723,7 +723,7 @@ async def test_if_fails_setup_bad_for(hass, calls):
async def test_if_fails_setup_for_without_above_below(hass, calls):
"""Test for setup failures for missing above or below."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py
index 53c1eaab3d9..4ce695afeb9 100644
--- a/tests/components/automation/test_state.py
+++ b/tests/components/automation/test_state.py
@@ -51,6 +51,7 @@ async def test_if_fires_on_entity_change(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world', context=context)
await hass.async_block_till_done()
@@ -80,6 +81,7 @@ async def test_if_fires_on_entity_change_with_from_filter(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -100,6 +102,7 @@ async def test_if_fires_on_entity_change_with_to_filter(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -120,6 +123,7 @@ async def test_if_fires_on_attribute_change_with_to_filter(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world', {'test_attribute': 11})
hass.states.async_set('test.entity', 'world', {'test_attribute': 12})
@@ -142,6 +146,7 @@ async def test_if_fires_on_entity_change_with_both_filters(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -163,6 +168,7 @@ async def test_if_not_fires_if_to_filter_not_match(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'moon')
await hass.async_block_till_done()
@@ -186,6 +192,7 @@ async def test_if_not_fires_if_from_filter_not_match(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -205,6 +212,7 @@ async def test_if_not_fires_if_entity_not_match(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -231,6 +239,7 @@ async def test_if_action(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set(entity_id, test_state)
hass.bus.async_fire('test_event')
@@ -247,7 +256,7 @@ async def test_if_action(hass, calls):
async def test_if_fails_setup_if_to_boolean_value(hass, calls):
"""Test for setup failure for boolean to."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
@@ -263,7 +272,7 @@ async def test_if_fails_setup_if_to_boolean_value(hass, calls):
async def test_if_fails_setup_if_from_boolean_value(hass, calls):
"""Test for setup failure for boolean from."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
@@ -279,7 +288,7 @@ async def test_if_fails_setup_if_from_boolean_value(hass, calls):
async def test_if_fails_setup_bad_for(hass, calls):
"""Test for setup failure for bad for."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
@@ -298,7 +307,7 @@ async def test_if_fails_setup_bad_for(hass, calls):
async def test_if_fails_setup_for_without_to(hass, calls):
"""Test for setup failures for missing to."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
@@ -331,6 +340,7 @@ async def test_if_not_fires_on_entity_change_with_for(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -362,6 +372,7 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(hass,
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity_1', 'world')
hass.states.async_set('test.entity_2', 'world')
@@ -402,6 +413,7 @@ async def test_if_fires_on_entity_change_with_for_attribute_change(hass,
}
}
})
+ await hass.async_block_till_done()
utcnow = dt_util.utcnow()
with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow:
@@ -438,6 +450,7 @@ async def test_if_fires_on_entity_change_with_for_multiple_force_update(hass,
}
}
})
+ await hass.async_block_till_done()
utcnow = dt_util.utcnow()
with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow:
@@ -473,6 +486,7 @@ async def test_if_fires_on_entity_change_with_for(hass, calls):
}
}
})
+ await hass.async_block_till_done()
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
@@ -505,6 +519,7 @@ async def test_if_fires_on_for_condition(hass, calls):
'action': {'service': 'test.automation'},
}
})
+ await hass.async_block_till_done()
# not enough time has passed
hass.bus.async_fire('test_event')
@@ -543,6 +558,7 @@ async def test_if_fires_on_for_condition_attribute_change(hass, calls):
'action': {'service': 'test.automation'},
}
})
+ await hass.async_block_till_done()
# not enough time has passed
hass.bus.async_fire('test_event')
@@ -566,7 +582,7 @@ async def test_if_fires_on_for_condition_attribute_change(hass, calls):
async def test_if_fails_setup_for_without_time(hass, calls):
"""Test for setup failure if no time is provided."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
@@ -585,7 +601,7 @@ async def test_if_fails_setup_for_without_time(hass, calls):
async def test_if_fails_setup_for_without_entity(hass, calls):
"""Test for setup failure if no entity is provided."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {'event_type': 'bla'},
diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py
index f803f97f4ab..25f32ac1939 100644
--- a/tests/components/automation/test_template.py
+++ b/tests/components/automation/test_template.py
@@ -369,7 +369,7 @@ async def test_if_action(hass, calls):
async def test_if_fires_on_change_with_bad_template(hass, calls):
"""Test for firing on change with bad template."""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py
index 0e570800342..cc5ef302d81 100644
--- a/tests/components/automation/test_time.py
+++ b/tests/components/automation/test_time.py
@@ -56,7 +56,7 @@ async def test_if_not_fires_using_wrong_at(hass, calls):
This should break the before rule.
"""
- with assert_setup_component(0):
+ with assert_setup_component(0, automation.DOMAIN):
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
diff --git a/tests/components/automation/test_webhook.py b/tests/components/automation/test_webhook.py
index a6cde395583..bed8de18ed4 100644
--- a/tests/components/automation/test_webhook.py
+++ b/tests/components/automation/test_webhook.py
@@ -1,8 +1,17 @@
"""The tests for the webhook automation trigger."""
+import pytest
+
from homeassistant.core import callback
from homeassistant.setup import async_setup_component
+@pytest.fixture(autouse=True)
+async def setup_http(hass):
+ """Set up http."""
+ assert await async_setup_component(hass, 'http', {})
+ assert await async_setup_component(hass, 'webhook', {})
+
+
async def test_webhook_json(hass, aiohttp_client):
"""Test triggering with a JSON webhook."""
events = []
@@ -73,3 +82,37 @@ async def test_webhook_post(hass, aiohttp_client):
assert len(events) == 1
assert events[0].data['hello'] == 'yo world'
+
+
+async def test_webhook_query(hass, aiohttp_client):
+ """Test triggering with a query POST webhook."""
+ events = []
+
+ @callback
+ def store_event(event):
+ """Helepr to store events."""
+ events.append(event)
+
+ hass.bus.async_listen('test_success', store_event)
+
+ assert await async_setup_component(hass, 'automation', {
+ 'automation': {
+ 'trigger': {
+ 'platform': 'webhook',
+ 'webhook_id': 'query_webhook'
+ },
+ 'action': {
+ 'event': 'test_success',
+ 'event_data_template': {
+ 'hello': 'yo {{ trigger.query.hello }}',
+ }
+ }
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ await client.post('/api/webhook/query_webhook?hello=world')
+
+ assert len(events) == 1
+ assert events[0].data['hello'] == 'yo world'
diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py
index 89dd9deaa0a..9a0bf2ccee2 100644
--- a/tests/components/aws/test_init.py
+++ b/tests/components/aws/test_init.py
@@ -10,15 +10,19 @@ class MockAioSession:
def __init__(self, *args, **kwargs):
"""Init a mock session."""
+ self.get_user = CoroutineMock()
+ self.invoke = CoroutineMock()
+ self.publish = CoroutineMock()
+ self.send_message = CoroutineMock()
def create_client(self, *args, **kwargs): # pylint: disable=no-self-use
"""Create a mocked client."""
return MagicMock(
__aenter__=CoroutineMock(return_value=CoroutineMock(
- get_user=CoroutineMock(), # iam
- invoke=CoroutineMock(), # lambda
- publish=CoroutineMock(), # sns
- send_message=CoroutineMock(), # sqs
+ get_user=self.get_user, # iam
+ invoke=self.invoke, # lambda
+ publish=self.publish, # sns
+ send_message=self.send_message, # sqs
)),
__aexit__=CoroutineMock()
)
@@ -35,7 +39,10 @@ async def test_empty_config(hass):
sessions = hass.data[aws.DATA_SESSIONS]
assert sessions is not None
assert len(sessions) == 1
- assert isinstance(sessions.get('default'), MockAioSession)
+ session = sessions.get('default')
+ assert isinstance(session, MockAioSession)
+ # we don't validate auto-created default profile
+ session.get_user.assert_not_awaited()
async def test_empty_credential(hass):
@@ -55,7 +62,8 @@ async def test_empty_credential(hass):
sessions = hass.data[aws.DATA_SESSIONS]
assert sessions is not None
assert len(sessions) == 1
- assert isinstance(sessions.get('default'), MockAioSession)
+ session = sessions.get('default')
+ assert isinstance(session, MockAioSession)
assert hass.services.has_service('notify', 'new_lambda_test') is True
await hass.services.async_call(
@@ -64,6 +72,7 @@ async def test_empty_credential(hass):
{'message': 'test', 'target': 'ARN'},
blocking=True
)
+ session.invoke.assert_awaited_once()
async def test_profile_credential(hass):
@@ -88,7 +97,8 @@ async def test_profile_credential(hass):
sessions = hass.data[aws.DATA_SESSIONS]
assert sessions is not None
assert len(sessions) == 1
- assert isinstance(sessions.get('test'), MockAioSession)
+ session = sessions.get('test')
+ assert isinstance(session, MockAioSession)
assert hass.services.has_service('notify', 'sns_test') is True
await hass.services.async_call(
@@ -97,6 +107,7 @@ async def test_profile_credential(hass):
{'title': 'test', 'message': 'test', 'target': 'ARN'},
blocking=True
)
+ session.publish.assert_awaited_once()
async def test_access_key_credential(hass):
@@ -128,7 +139,8 @@ async def test_access_key_credential(hass):
sessions = hass.data[aws.DATA_SESSIONS]
assert sessions is not None
assert len(sessions) == 2
- assert isinstance(sessions.get('key'), MockAioSession)
+ session = sessions.get('key')
+ assert isinstance(session, MockAioSession)
assert hass.services.has_service('notify', 'sns_test') is True
await hass.services.async_call(
@@ -137,6 +149,7 @@ async def test_access_key_credential(hass):
{'title': 'test', 'message': 'test', 'target': 'ARN'},
blocking=True
)
+ session.publish.assert_awaited_once()
async def test_notify_credential(hass):
@@ -197,3 +210,28 @@ async def test_notify_credential_profile(hass):
{'message': 'test', 'target': 'ARN'},
blocking=True
)
+
+
+async def test_credential_skip_validate(hass):
+ """Test credential can skip validate."""
+ with async_patch('aiobotocore.AioSession', new=MockAioSession):
+ await async_setup_component(hass, 'aws', {
+ 'aws': {
+ 'credentials': [
+ {
+ 'name': 'key',
+ 'aws_access_key_id': 'not-valid',
+ 'aws_secret_access_key': 'dont-care',
+ 'validate': False
+ },
+ ],
+ }
+ })
+ await hass.async_block_till_done()
+
+ sessions = hass.data[aws.DATA_SESSIONS]
+ assert sessions is not None
+ assert len(sessions) == 1
+ session = sessions.get('key')
+ assert isinstance(session, MockAioSession)
+ session.get_user.assert_not_awaited()
diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py
index 9ca8b81793b..75dd6462c4e 100644
--- a/tests/components/axis/test_binary_sensor.py
+++ b/tests/components/axis/test_binary_sensor.py
@@ -53,9 +53,9 @@ async def setup_device(hass):
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
device = axis.AxisNetworkDevice(hass, config_entry)
- device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE],
- signal=device.async_signal_callback)
+ device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE])
hass.data[axis.DOMAIN] = {device.serial: device}
+ device.api.enable_events(event_callback=device.async_event_callback)
await hass.config_entries.async_forward_entry_setup(
config_entry, 'binary_sensor')
diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py
index c585ada6319..95878697e03 100644
--- a/tests/components/axis/test_camera.py
+++ b/tests/components/axis/test_camera.py
@@ -37,9 +37,9 @@ async def setup_device(hass):
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
device = axis.AxisNetworkDevice(hass, config_entry)
- device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE],
- signal=device.async_signal_callback)
+ device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE])
hass.data[axis.DOMAIN] = {device.serial: device}
+ device.api.enable_events(event_callback=device.async_event_callback)
await hass.config_entries.async_forward_entry_setup(
config_entry, 'camera')
diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py
index 7e18b36c6a6..1a83e9be8b5 100644
--- a/tests/components/axis/test_config_flow.py
+++ b/tests/components/axis/test_config_flow.py
@@ -6,8 +6,6 @@ from homeassistant.components.axis import config_flow
from tests.common import mock_coro, MockConfigEntry
-import axis as axis_lib
-
async def test_configured_devices(hass):
"""Test that configured devices works as expected."""
@@ -16,7 +14,7 @@ async def test_configured_devices(hass):
assert not result
entry = MockConfigEntry(domain=axis.DOMAIN,
- data={axis.CONF_DEVICE: {axis.CONF_HOST: ''}})
+ data={axis.config_flow.CONF_MAC: '1234'})
entry.add_to_hass(hass)
result = config_flow.configured_devices(hass)
@@ -26,13 +24,9 @@ async def test_configured_devices(hass):
async def test_flow_works(hass):
"""Test that config flow works."""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
with patch('axis.AxisDevice') as mock_device:
def mock_constructor(
- loop, host, username, password, port, web_proto, event_types,
- signal):
+ loop, host, username, password, port, web_proto):
"""Fake the controller constructor."""
mock_device.loop = loop
mock_device.host = host
@@ -41,34 +35,40 @@ async def test_flow_works(hass):
mock_device.port = port
return mock_device
- def mock_get_param(param):
- """Fake get param method."""
- return param
-
mock_device.side_effect = mock_constructor
- mock_device.vapix.load_params.return_value = Mock()
- mock_device.vapix.get_param.side_effect = mock_get_param
+ mock_device.vapix.params.system_serialnumber = 'serialnumber'
+ mock_device.vapix.params.prodnbr = 'prodnbr'
- result = await flow.async_step_user(user_input={
- config_flow.CONF_HOST: '1.2.3.4',
- config_flow.CONF_USERNAME: 'user',
- config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ context={'source': 'user'}
+ )
+
+ assert result['type'] == 'form'
+ assert result['step_id'] == 'user'
+
+ result = await hass.config_entries.flow.async_configure(
+ result['flow_id'],
+ user_input={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_USERNAME: 'user',
+ config_flow.CONF_PASSWORD: 'pass',
+ config_flow.CONF_PORT: 80
+ }
+ )
assert result['type'] == 'create_entry'
- assert result['title'] == '{} - {}'.format(
- axis_lib.vapix.VAPIX_MODEL_ID, axis_lib.vapix.VAPIX_SERIAL_NUMBER)
+ assert result['title'] == '{} - {}'.format('prodnbr', 'serialnumber')
assert result['data'] == {
axis.CONF_DEVICE: {
config_flow.CONF_HOST: '1.2.3.4',
config_flow.CONF_USERNAME: 'user',
config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
+ config_flow.CONF_PORT: 80
},
- config_flow.CONF_MAC: axis_lib.vapix.VAPIX_SERIAL_NUMBER,
- config_flow.CONF_MODEL: axis_lib.vapix.VAPIX_MODEL_ID,
- config_flow.CONF_NAME: 'Brand.ProdNbr 0'
+ config_flow.CONF_MAC: 'serialnumber',
+ config_flow.CONF_MODEL: 'prodnbr',
+ config_flow.CONF_NAME: 'prodnbr 0'
}
@@ -77,17 +77,21 @@ async def test_flow_fails_already_configured(hass):
flow = config_flow.AxisFlowHandler()
flow.hass = hass
- entry = MockConfigEntry(domain=axis.DOMAIN, data={axis.CONF_DEVICE: {
- axis.CONF_HOST: '1.2.3.4'
- }})
+ entry = MockConfigEntry(domain=axis.DOMAIN,
+ data={axis.config_flow.CONF_MAC: '1234'})
entry.add_to_hass(hass)
- result = await flow.async_step_user(user_input={
- config_flow.CONF_HOST: '1.2.3.4',
- config_flow.CONF_USERNAME: 'user',
- config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
- })
+ mock_device = Mock()
+ mock_device.vapix.params.system_serialnumber = '1234'
+
+ with patch('homeassistant.components.axis.config_flow.get_device',
+ return_value=mock_coro(mock_device)):
+ result = await flow.async_step_user(user_input={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_USERNAME: 'user',
+ config_flow.CONF_PASSWORD: 'pass',
+ config_flow.CONF_PORT: 80
+ })
assert result['errors'] == {'base': 'already_configured'}
@@ -103,7 +107,7 @@ async def test_flow_fails_faulty_credentials(hass):
config_flow.CONF_HOST: '1.2.3.4',
config_flow.CONF_USERNAME: 'user',
config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
+ config_flow.CONF_PORT: 80
})
assert result['errors'] == {'base': 'faulty_credentials'}
@@ -120,7 +124,7 @@ async def test_flow_fails_device_unavailable(hass):
config_flow.CONF_HOST: '1.2.3.4',
config_flow.CONF_USERNAME: 'user',
config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
+ config_flow.CONF_PORT: 80
})
assert result['errors'] == {'base': 'device_unavailable'}
@@ -159,15 +163,16 @@ async def test_flow_create_entry_more_entries(hass):
async def test_discovery_flow(hass):
"""Test that discovery for new devices work."""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
with patch.object(axis, 'get_device', return_value=mock_coro(Mock())):
- result = await flow.async_step_discovery(discovery_info={
- config_flow.CONF_HOST: '1.2.3.4',
- config_flow.CONF_PORT: 80,
- 'properties': {'macaddress': '1234'}
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ 'properties': {'macaddress': '1234'}
+ },
+ context={'source': 'discovery'}
+ )
assert result['type'] == 'form'
assert result['step_id'] == 'user'
@@ -178,9 +183,6 @@ async def test_discovery_flow_known_device(hass):
This is legacy support from devices registered with configurator.
"""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
with patch('homeassistant.components.axis.config_flow.load_json',
return_value={'1234ABCD': {
config_flow.CONF_HOST: '2.3.4.5',
@@ -189,8 +191,7 @@ async def test_discovery_flow_known_device(hass):
config_flow.CONF_PORT: 80}}), \
patch('axis.AxisDevice') as mock_device:
def mock_constructor(
- loop, host, username, password, port, web_proto, event_types,
- signal):
+ loop, host, username, password, port, web_proto):
"""Fake the controller constructor."""
mock_device.loop = loop
mock_device.host = host
@@ -199,61 +200,62 @@ async def test_discovery_flow_known_device(hass):
mock_device.port = port
return mock_device
- def mock_get_param(param):
- """Fake get param method."""
- return param
-
mock_device.side_effect = mock_constructor
- mock_device.vapix.load_params.return_value = Mock()
- mock_device.vapix.get_param.side_effect = mock_get_param
- result = await flow.async_step_discovery(discovery_info={
- config_flow.CONF_HOST: '1.2.3.4',
- config_flow.CONF_PORT: 80,
- 'hostname': 'name',
- 'properties': {'macaddress': '1234ABCD'}
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ 'hostname': 'name',
+ 'properties': {'macaddress': '1234ABCD'}
+ },
+ context={'source': 'discovery'}
+ )
assert result['type'] == 'create_entry'
async def test_discovery_flow_already_configured(hass):
"""Test that discovery doesn't setup already configured devices."""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
- entry = MockConfigEntry(domain=axis.DOMAIN, data={axis.CONF_DEVICE: {
- axis.CONF_HOST: '1.2.3.4'
- }})
+ entry = MockConfigEntry(
+ domain=axis.DOMAIN,
+ data={axis.CONF_DEVICE: {axis.config_flow.CONF_HOST: '1.2.3.4'},
+ axis.config_flow.CONF_MAC: '1234ABCD'}
+ )
entry.add_to_hass(hass)
- result = await flow.async_step_discovery(discovery_info={
- config_flow.CONF_HOST: '1.2.3.4',
- config_flow.CONF_USERNAME: 'user',
- config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
- })
- print(result)
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_USERNAME: 'user',
+ config_flow.CONF_PASSWORD: 'pass',
+ config_flow.CONF_PORT: 80,
+ 'hostname': 'name',
+ 'properties': {'macaddress': '1234ABCD'}
+ },
+ context={'source': 'discovery'}
+ )
+
assert result['type'] == 'abort'
+ assert result['reason'] == 'already_configured'
-async def test_discovery_flow_link_local_address(hass):
+async def test_discovery_flow_ignore_link_local_address(hass):
"""Test that discovery doesn't setup devices with link local addresses."""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
- result = await flow.async_step_discovery(discovery_info={
- config_flow.CONF_HOST: '169.254.3.4'
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={config_flow.CONF_HOST: '169.254.3.4'},
+ context={'source': 'discovery'}
+ )
assert result['type'] == 'abort'
+ assert result['reason'] == 'link_local_address'
async def test_discovery_flow_bad_config_file(hass):
"""Test that discovery with bad config files abort."""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
with patch('homeassistant.components.axis.config_flow.load_json',
return_value={'1234ABCD': {
config_flow.CONF_HOST: '2.3.4.5',
@@ -262,23 +264,24 @@ async def test_discovery_flow_bad_config_file(hass):
config_flow.CONF_PORT: 80}}), \
patch('homeassistant.components.axis.config_flow.DEVICE_SCHEMA',
side_effect=config_flow.vol.Invalid('')):
- result = await flow.async_step_discovery(discovery_info={
- config_flow.CONF_HOST: '1.2.3.4',
- 'properties': {'macaddress': '1234ABCD'}
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: '1.2.3.4',
+ 'properties': {'macaddress': '1234ABCD'}
+ },
+ context={'source': 'discovery'}
+ )
assert result['type'] == 'abort'
+ assert result['reason'] == 'bad_config_file'
async def test_import_flow_works(hass):
"""Test that import flow works."""
- flow = config_flow.AxisFlowHandler()
- flow.hass = hass
-
with patch('axis.AxisDevice') as mock_device:
def mock_constructor(
- loop, host, username, password, port, web_proto, event_types,
- signal):
+ loop, host, username, password, port, web_proto):
"""Fake the controller constructor."""
mock_device.loop = loop
mock_device.host = host
@@ -287,33 +290,32 @@ async def test_import_flow_works(hass):
mock_device.port = port
return mock_device
- def mock_get_param(param):
- """Fake get param method."""
- return param
-
mock_device.side_effect = mock_constructor
- mock_device.vapix.load_params.return_value = Mock()
- mock_device.vapix.get_param.side_effect = mock_get_param
+ mock_device.vapix.params.system_serialnumber = 'serialnumber'
+ mock_device.vapix.params.prodnbr = 'prodnbr'
- result = await flow.async_step_import(import_config={
- config_flow.CONF_HOST: '1.2.3.4',
- config_flow.CONF_USERNAME: 'user',
- config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81,
- config_flow.CONF_NAME: 'name'
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_USERNAME: 'user',
+ config_flow.CONF_PASSWORD: 'pass',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_NAME: 'name'
+ },
+ context={'source': 'import'}
+ )
assert result['type'] == 'create_entry'
- assert result['title'] == '{} - {}'.format(
- axis_lib.vapix.VAPIX_MODEL_ID, axis_lib.vapix.VAPIX_SERIAL_NUMBER)
+ assert result['title'] == '{} - {}'.format('prodnbr', 'serialnumber')
assert result['data'] == {
axis.CONF_DEVICE: {
config_flow.CONF_HOST: '1.2.3.4',
config_flow.CONF_USERNAME: 'user',
config_flow.CONF_PASSWORD: 'pass',
- config_flow.CONF_PORT: 81
+ config_flow.CONF_PORT: 80
},
- config_flow.CONF_MAC: axis_lib.vapix.VAPIX_SERIAL_NUMBER,
- config_flow.CONF_MODEL: axis_lib.vapix.VAPIX_MODEL_ID,
+ config_flow.CONF_MAC: 'serialnumber',
+ config_flow.CONF_MODEL: 'prodnbr',
config_flow.CONF_NAME: 'name'
}
diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py
index 2a0a7d6391c..23714e51c88 100644
--- a/tests/components/axis/test_device.py
+++ b/tests/components/axis/test_device.py
@@ -3,9 +3,10 @@ from unittest.mock import Mock, patch
import pytest
-from tests.common import mock_coro
+from tests.common import mock_coro, MockConfigEntry
from homeassistant.components.axis import device, errors
+from homeassistant.components.axis.camera import AxisCamera
DEVICE_DATA = {
device.CONF_HOST: '1.2.3.4',
@@ -16,7 +17,7 @@ DEVICE_DATA = {
ENTRY_OPTIONS = {
device.CONF_CAMERA: True,
- device.CONF_EVENTS: ['pir'],
+ device.CONF_EVENTS: True,
}
ENTRY_CONFIG = {
@@ -53,6 +54,78 @@ async def test_device_setup():
(entry, 'binary_sensor')
+async def test_device_signal_new_address(hass):
+ """Successful setup."""
+ entry = MockConfigEntry(
+ domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS)
+
+ api = Mock()
+ api.vapix.get_param.return_value = '1234'
+
+ axis_device = device.AxisNetworkDevice(hass, entry)
+ hass.data[device.DOMAIN] = {axis_device.serial: axis_device}
+
+ with patch.object(device, 'get_device', return_value=mock_coro(api)), \
+ patch.object(AxisCamera, '_new_address') as new_address_mock:
+ await axis_device.async_setup()
+ await hass.async_block_till_done()
+
+ assert len(hass.states.async_all()) == 1
+ assert len(axis_device.listeners) == 1
+
+ entry.data[device.CONF_DEVICE][device.CONF_HOST] = '2.3.4.5'
+ hass.config_entries.async_update_entry(entry, data=entry.data)
+ await hass.async_block_till_done()
+
+ assert axis_device.host == '2.3.4.5'
+ assert axis_device.api.config.host == '2.3.4.5'
+ assert len(new_address_mock.mock_calls) == 1
+
+
+async def test_device_unavailable(hass):
+ """Successful setup."""
+ entry = MockConfigEntry(
+ domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS)
+
+ api = Mock()
+ api.vapix.get_param.return_value = '1234'
+
+ axis_device = device.AxisNetworkDevice(hass, entry)
+ hass.data[device.DOMAIN] = {axis_device.serial: axis_device}
+
+ with patch.object(device, 'get_device', return_value=mock_coro(api)), \
+ patch.object(device, 'async_dispatcher_send') as mock_dispatcher:
+ await axis_device.async_setup()
+ await hass.async_block_till_done()
+
+ axis_device.async_connection_status_callback(status=False)
+
+ assert not axis_device.available
+ assert len(mock_dispatcher.mock_calls) == 1
+
+
+async def test_device_reset(hass):
+ """Successfully reset device."""
+ entry = MockConfigEntry(
+ domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS)
+
+ api = Mock()
+ api.vapix.get_param.return_value = '1234'
+
+ axis_device = device.AxisNetworkDevice(hass, entry)
+ hass.data[device.DOMAIN] = {axis_device.serial: axis_device}
+
+ with patch.object(device, 'get_device', return_value=mock_coro(api)):
+ await axis_device.async_setup()
+ await hass.async_block_till_done()
+
+ await axis_device.async_reset()
+
+ assert len(api.stop.mock_calls) == 1
+ assert len(hass.states.async_all()) == 0
+ assert len(axis_device.listeners) == 0
+
+
async def test_device_not_accessible():
"""Failed setup schedules a retry of setup."""
hass = Mock()
@@ -94,7 +167,7 @@ async def test_new_event_sends_signal(hass):
axis_device = device.AxisNetworkDevice(hass, entry)
with patch.object(device, 'async_dispatcher_send') as mock_dispatch_send:
- axis_device.async_signal_callback(action='add', event='event')
+ axis_device.async_event_callback(action='add', event_id='event')
await hass.async_block_till_done()
assert len(mock_dispatch_send.mock_calls) == 1
@@ -117,8 +190,10 @@ async def test_shutdown():
async def test_get_device(hass):
"""Successful call."""
- with patch('axis.vapix.Vapix.load_params',
- return_value=mock_coro()):
+ with patch('axis.param_cgi.Params.update_brand',
+ return_value=mock_coro()), \
+ patch('axis.param_cgi.Params.update_properties',
+ return_value=mock_coro()):
assert await device.get_device(hass, DEVICE_DATA)
@@ -126,7 +201,7 @@ async def test_get_device_fails(hass):
"""Device unauthorized yields authentication required error."""
import axis
- with patch('axis.vapix.Vapix.load_params',
+ with patch('axis.param_cgi.Params.update_brand',
side_effect=axis.Unauthorized), \
pytest.raises(errors.AuthenticationRequired):
await device.get_device(hass, DEVICE_DATA)
@@ -136,7 +211,7 @@ async def test_get_device_device_unavailable(hass):
"""Device unavailable yields cannot connect error."""
import axis
- with patch('axis.vapix.Vapix.load_params',
+ with patch('axis.param_cgi.Params.update_brand',
side_effect=axis.RequestError), \
pytest.raises(errors.CannotConnect):
await device.get_device(hass, DEVICE_DATA)
@@ -146,7 +221,7 @@ async def test_get_device_unknown_error(hass):
"""Device yield unknown error."""
import axis
- with patch('axis.vapix.Vapix.load_params',
+ with patch('axis.param_cgi.Params.update_brand',
side_effect=axis.AxisException), \
pytest.raises(errors.AuthenticationRequired):
await device.get_device(hass, DEVICE_DATA)
diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py
index c1c4c06f6ac..0fc57df2ff0 100644
--- a/tests/components/axis/test_init.py
+++ b/tests/components/axis/test_init.py
@@ -9,30 +9,28 @@ from tests.common import mock_coro, MockConfigEntry
async def test_setup(hass):
"""Test configured options for a device are loaded via config entry."""
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(axis, 'configured_devices', return_value={}):
+ with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, axis.DOMAIN, {
axis.DOMAIN: {
'device_name': {
- axis.CONF_HOST: '1.2.3.4',
+ axis.config_flow.CONF_HOST: '1.2.3.4',
axis.config_flow.CONF_PORT: 80,
}
}
})
- assert len(mock_config_entries.flow.mock_calls) == 1
+ assert len(mock_config_flow.mock_calls) == 1
async def test_setup_device_already_configured(hass):
"""Test already configured device does not configure a second."""
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(axis, 'configured_devices', return_value={'1.2.3.4'}):
+ with patch.object(hass, 'config_entries') as mock_config_entries:
assert await async_setup_component(hass, axis.DOMAIN, {
axis.DOMAIN: {
'device_name': {
- axis.CONF_HOST: '1.2.3.4'
+ axis.config_flow.CONF_HOST: '1.2.3.4'
}
}
})
@@ -51,10 +49,11 @@ async def test_setup_entry(hass):
entry = MockConfigEntry(
domain=axis.DOMAIN, data={axis.device.CONF_MAC: '0123'})
- mock_device = Mock()
- mock_device.async_setup.return_value = mock_coro(True)
- mock_device.async_update_device_registry.return_value = mock_coro(True)
- mock_device.serial.return_value = '1'
+ mock_device = axis.AxisNetworkDevice(hass, entry)
+ mock_device.async_setup = Mock(return_value=mock_coro(True))
+ mock_device.async_update_device_registry = \
+ Mock(return_value=mock_coro(True))
+ mock_device.async_reset = Mock(return_value=mock_coro(True))
with patch.object(axis, 'AxisNetworkDevice') as mock_device_class, \
patch.object(
@@ -64,6 +63,7 @@ async def test_setup_entry(hass):
assert await axis.async_setup_entry(hass, entry)
assert len(hass.data[axis.DOMAIN]) == 1
+ assert '0123' in hass.data[axis.DOMAIN]
async def test_setup_entry_fails(hass):
@@ -82,6 +82,28 @@ async def test_setup_entry_fails(hass):
assert not hass.data[axis.DOMAIN]
+async def test_unload_entry(hass):
+ """Test successful unload of entry."""
+ entry = MockConfigEntry(
+ domain=axis.DOMAIN, data={axis.device.CONF_MAC: '0123'})
+
+ mock_device = axis.AxisNetworkDevice(hass, entry)
+ mock_device.async_setup = Mock(return_value=mock_coro(True))
+ mock_device.async_update_device_registry = \
+ Mock(return_value=mock_coro(True))
+ mock_device.async_reset = Mock(return_value=mock_coro(True))
+
+ with patch.object(axis, 'AxisNetworkDevice') as mock_device_class, \
+ patch.object(
+ axis, 'async_populate_options', return_value=mock_coro(True)):
+ mock_device_class.return_value = mock_device
+
+ assert await axis.async_setup_entry(hass, entry)
+
+ assert await axis.async_unload_entry(hass, entry)
+ assert not hass.data[axis.DOMAIN]
+
+
async def test_populate_options(hass):
"""Test successful populate options."""
entry = MockConfigEntry(domain=axis.DOMAIN, data={'device': {}})
diff --git a/tests/components/broadlink/__init__.py b/tests/components/broadlink/__init__.py
new file mode 100644
index 00000000000..c2d16b9ab2a
--- /dev/null
+++ b/tests/components/broadlink/__init__.py
@@ -0,0 +1 @@
+"""The tests for broadlink platforms."""
diff --git a/tests/components/broadlink/test_init.py b/tests/components/broadlink/test_init.py
new file mode 100644
index 00000000000..5dca559cb0e
--- /dev/null
+++ b/tests/components/broadlink/test_init.py
@@ -0,0 +1,117 @@
+"""The tests for the broadlink component."""
+from datetime import timedelta
+from base64 import b64decode
+from unittest.mock import MagicMock, patch, call
+
+import pytest
+import voluptuous as vol
+
+from homeassistant.util.dt import utcnow
+from homeassistant.components.broadlink import async_setup_service
+from homeassistant.components.broadlink.const import (
+ DOMAIN, SERVICE_LEARN, SERVICE_SEND)
+
+DUMMY_IR_PACKET = ("JgBGAJKVETkRORA6ERQRFBEUERQRFBE5ETkQOhAVEBUQFREUEBUQ"
+ "OhEUERQRORE5EBURFBA6EBUQOhE5EBUQFRA6EDoRFBEADQUAAA==")
+DUMMY_HOST = "192.168.0.2"
+
+
+@pytest.fixture(autouse=True)
+def dummy_broadlink():
+ """Mock broadlink module so we don't have that dependency on tests."""
+ broadlink = MagicMock()
+ with patch.dict('sys.modules', {
+ 'broadlink': broadlink,
+ }):
+ yield broadlink
+
+
+async def test_send(hass):
+ """Test send service."""
+ mock_device = MagicMock()
+ mock_device.send_data.return_value = None
+
+ async_setup_service(hass, DUMMY_HOST, mock_device)
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(DOMAIN, SERVICE_SEND, {
+ "host": DUMMY_HOST,
+ "packet": (DUMMY_IR_PACKET)
+ })
+ await hass.async_block_till_done()
+
+ assert mock_device.send_data.call_count == 1
+ assert mock_device.send_data.call_args == call(
+ b64decode(DUMMY_IR_PACKET))
+
+
+async def test_learn(hass):
+ """Test learn service."""
+ mock_device = MagicMock()
+ mock_device.enter_learning.return_value = None
+ mock_device.check_data.return_value = b64decode(DUMMY_IR_PACKET)
+
+ with patch.object(hass.components.persistent_notification,
+ 'async_create') as mock_create:
+
+ async_setup_service(hass, DUMMY_HOST, mock_device)
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(DOMAIN, SERVICE_LEARN, {
+ "host": DUMMY_HOST,
+ })
+ await hass.async_block_till_done()
+
+ assert mock_device.enter_learning.call_count == 1
+ assert mock_device.enter_learning.call_args == call()
+
+ assert mock_create.call_count == 1
+ assert mock_create.call_args == call(
+ "Received packet is: {}".format(DUMMY_IR_PACKET),
+ title='Broadlink switch')
+
+
+async def test_learn_timeout(hass):
+ """Test learn service."""
+ mock_device = MagicMock()
+ mock_device.enter_learning.return_value = None
+ mock_device.check_data.return_value = None
+
+ async_setup_service(hass, DUMMY_HOST, mock_device)
+ await hass.async_block_till_done()
+
+ now = utcnow()
+
+ with patch.object(hass.components.persistent_notification,
+ 'async_create') as mock_create, \
+ patch('homeassistant.components.broadlink.utcnow') as mock_utcnow:
+
+ mock_utcnow.side_effect = [now, now + timedelta(20)]
+
+ await hass.services.async_call(DOMAIN, SERVICE_LEARN, {
+ "host": DUMMY_HOST,
+ })
+ await hass.async_block_till_done()
+
+ assert mock_device.enter_learning.call_count == 1
+ assert mock_device.enter_learning.call_args == call()
+
+ assert mock_create.call_count == 1
+ assert mock_create.call_args == call(
+ "No signal was received",
+ title='Broadlink switch')
+
+
+async def test_ipv4():
+ """Test ipv4 parsing."""
+ from homeassistant.components.broadlink import ipv4_address
+
+ schema = vol.Schema(ipv4_address)
+
+ for value in ('invalid', '1', '192', '192.168',
+ '192.168.0', '192.168.0.A'):
+ with pytest.raises(vol.MultipleInvalid):
+ schema(value)
+
+ for value in ('192.168.0.1', '10.0.0.1'):
+ schema(value)
diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py
index e730f39656e..e12cca75c61 100644
--- a/tests/components/camera/test_init.py
+++ b/tests/components/camera/test_init.py
@@ -157,7 +157,7 @@ def test_snapshot_service(hass, mock_camera):
async def test_websocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
"""Test camera_thumbnail websocket command."""
- await async_setup_component(hass, 'camera')
+ await async_setup_component(hass, 'camera', {})
client = await hass_ws_client(hass)
await client.send_json({
@@ -179,7 +179,7 @@ async def test_websocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
async def test_websocket_stream_no_source(hass, hass_ws_client,
mock_camera, mock_stream):
"""Test camera/stream websocket command."""
- await async_setup_component(hass, 'camera')
+ await async_setup_component(hass, 'camera', {})
with patch('homeassistant.components.camera.request_stream',
return_value='http://home.assistant/playlist.m3u8') \
@@ -203,7 +203,7 @@ async def test_websocket_stream_no_source(hass, hass_ws_client,
async def test_websocket_camera_stream(hass, hass_ws_client,
mock_camera, mock_stream):
"""Test camera/stream websocket command."""
- await async_setup_component(hass, 'camera')
+ await async_setup_component(hass, 'camera', {})
with patch('homeassistant.components.camera.request_stream',
return_value='http://home.assistant/playlist.m3u8'
@@ -231,7 +231,7 @@ async def test_websocket_camera_stream(hass, hass_ws_client,
async def test_websocket_get_prefs(hass, hass_ws_client,
mock_camera):
"""Test get camera preferences websocket command."""
- await async_setup_component(hass, 'camera')
+ await async_setup_component(hass, 'camera', {})
# Request preferences through websocket
client = await hass_ws_client(hass)
@@ -249,7 +249,7 @@ async def test_websocket_get_prefs(hass, hass_ws_client,
async def test_websocket_update_prefs(hass, hass_ws_client,
mock_camera, setup_camera_prefs):
"""Test updating preference."""
- await async_setup_component(hass, 'camera')
+ await async_setup_component(hass, 'camera', {})
assert setup_camera_prefs[PREF_PRELOAD_STREAM]
client = await hass_ws_client(hass)
await client.send_json({
@@ -281,7 +281,7 @@ async def test_play_stream_service_no_source(hass, mock_camera, mock_stream):
async def test_handle_play_stream_service(hass, mock_camera, mock_stream):
"""Test camera play_stream service."""
- await async_setup_component(hass, 'media_player')
+ await async_setup_component(hass, 'media_player', {})
data = {
ATTR_ENTITY_ID: 'camera.demo_camera',
camera.ATTR_MEDIA_PLAYER: 'media_player.test'
diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py
index ff81c056420..78140d49e4a 100644
--- a/tests/components/cast/test_media_player.py
+++ b/tests/components/cast/test_media_player.py
@@ -24,12 +24,14 @@ def cast_mock():
"""Mock pychromecast."""
with patch.dict('sys.modules', {
'pychromecast': MagicMock(),
+ 'pychromecast.controllers.multizone': MagicMock(),
}):
yield
# pylint: disable=invalid-name
FakeUUID = UUID('57355bce-9364-4aa6-ac1e-eb849dccf9e2')
+FakeGroupUUID = UUID('57355bce-9364-4aa6-ac1e-eb849dccf9e3')
def get_fake_chromecast(info: ChromecastInfo):
@@ -300,6 +302,208 @@ async def test_entity_media_states(hass: HomeAssistantType):
assert state.state == 'unknown'
+async def test_group_media_states(hass: HomeAssistantType):
+ """Test media states are read from group if entity has no state."""
+ info = get_fake_chromecast_info()
+ full_info = attr.evolve(info, model_name='google home',
+ friendly_name='Speaker', uuid=FakeUUID)
+
+ with patch('pychromecast.dial.get_device_status',
+ return_value=full_info):
+ chromecast, entity = await async_setup_media_player_cast(hass, info)
+
+ entity._available = True
+ entity.schedule_update_ha_state()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('media_player.speaker')
+ assert state is not None
+ assert state.name == 'Speaker'
+ assert state.state == 'unknown'
+ assert entity.unique_id == full_info.uuid
+
+ group_media_status = MagicMock(images=None)
+ player_media_status = MagicMock(images=None)
+
+ # Player has no state, group is playing -> Should report 'playing'
+ group_media_status.player_is_playing = True
+ entity.multizone_new_media_status(str(FakeGroupUUID), group_media_status)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.speaker')
+ assert state.state == 'playing'
+
+ # Player is paused, group is playing -> Should report 'paused'
+ player_media_status.player_is_playing = False
+ player_media_status.player_is_paused = True
+ entity.new_media_status(player_media_status)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.speaker')
+ assert state.state == 'paused'
+
+ # Player is in unknown state, group is playing -> Should report 'playing'
+ player_media_status.player_state = "UNKNOWN"
+ entity.new_media_status(player_media_status)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.speaker')
+ assert state.state == 'playing'
+
+
+async def test_dynamic_group_media_states(hass: HomeAssistantType):
+ """Test media states are read from group if entity has no state."""
+ info = get_fake_chromecast_info()
+ full_info = attr.evolve(info, model_name='google home',
+ friendly_name='Speaker', uuid=FakeUUID)
+
+ with patch('pychromecast.dial.get_device_status',
+ return_value=full_info):
+ chromecast, entity = await async_setup_media_player_cast(hass, info)
+
+ entity._available = True
+ entity.schedule_update_ha_state()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('media_player.speaker')
+ assert state is not None
+ assert state.name == 'Speaker'
+ assert state.state == 'unknown'
+ assert entity.unique_id == full_info.uuid
+
+ group_media_status = MagicMock(images=None)
+ player_media_status = MagicMock(images=None)
+
+ # Player has no state, dynamic group is playing -> Should report 'playing'
+ entity._dynamic_group_cast = MagicMock()
+ group_media_status.player_is_playing = True
+ entity.new_dynamic_group_media_status(group_media_status)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.speaker')
+ assert state.state == 'playing'
+
+ # Player is paused, dynamic group is playing -> Should report 'paused'
+ player_media_status.player_is_playing = False
+ player_media_status.player_is_paused = True
+ entity.new_media_status(player_media_status)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.speaker')
+ assert state.state == 'paused'
+
+ # Player is in unknown state, dynamic group is playing -> Should report
+ # 'playing'
+ player_media_status.player_state = "UNKNOWN"
+ entity.new_media_status(player_media_status)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.speaker')
+ assert state.state == 'playing'
+
+
+async def test_group_media_control(hass: HomeAssistantType):
+ """Test media states are read from group if entity has no state."""
+ info = get_fake_chromecast_info()
+ full_info = attr.evolve(info, model_name='google home',
+ friendly_name='Speaker', uuid=FakeUUID)
+
+ with patch('pychromecast.dial.get_device_status',
+ return_value=full_info):
+ chromecast, entity = await async_setup_media_player_cast(hass, info)
+
+ entity._available = True
+ entity.schedule_update_ha_state()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('media_player.speaker')
+ assert state is not None
+ assert state.name == 'Speaker'
+ assert state.state == 'unknown'
+ assert entity.unique_id == full_info.uuid
+
+ group_media_status = MagicMock(images=None)
+ player_media_status = MagicMock(images=None)
+
+ # Player has no state, group is playing -> Should forward calls to group
+ group_media_status.player_is_playing = True
+ entity.multizone_new_media_status(str(FakeGroupUUID), group_media_status)
+ entity.media_play()
+ grp_media = entity.mz_mgr.get_multizone_mediacontroller(str(FakeGroupUUID))
+ assert grp_media.play.called
+ assert not chromecast.media_controller.play.called
+
+ # Player is paused, group is playing -> Should not forward
+ player_media_status.player_is_playing = False
+ player_media_status.player_is_paused = True
+ entity.new_media_status(player_media_status)
+ entity.media_pause()
+ grp_media = entity.mz_mgr.get_multizone_mediacontroller(str(FakeGroupUUID))
+ assert not grp_media.pause.called
+ assert chromecast.media_controller.pause.called
+
+ # Player is in unknown state, group is playing -> Should forward to group
+ player_media_status.player_state = "UNKNOWN"
+ entity.new_media_status(player_media_status)
+ entity.media_stop()
+ grp_media = entity.mz_mgr.get_multizone_mediacontroller(str(FakeGroupUUID))
+ assert grp_media.stop.called
+ assert not chromecast.media_controller.stop.called
+
+ # Verify play_media is not forwarded
+ entity.play_media(None, None)
+ assert not grp_media.play_media.called
+ assert chromecast.media_controller.play_media.called
+
+
+async def test_dynamic_group_media_control(hass: HomeAssistantType):
+ """Test media states are read from group if entity has no state."""
+ info = get_fake_chromecast_info()
+ full_info = attr.evolve(info, model_name='google home',
+ friendly_name='Speaker', uuid=FakeUUID)
+
+ with patch('pychromecast.dial.get_device_status',
+ return_value=full_info):
+ chromecast, entity = await async_setup_media_player_cast(hass, info)
+
+ entity._available = True
+ entity.schedule_update_ha_state()
+ entity._dynamic_group_cast = MagicMock()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('media_player.speaker')
+ assert state is not None
+ assert state.name == 'Speaker'
+ assert state.state == 'unknown'
+ assert entity.unique_id == full_info.uuid
+
+ group_media_status = MagicMock(images=None)
+ player_media_status = MagicMock(images=None)
+
+ # Player has no state, dynamic group is playing -> Should forward
+ group_media_status.player_is_playing = True
+ entity.new_dynamic_group_media_status(group_media_status)
+ entity.media_previous_track()
+ assert entity._dynamic_group_cast.media_controller.queue_prev.called
+ assert not chromecast.media_controller.queue_prev.called
+
+ # Player is paused, dynamic group is playing -> Should not forward
+ player_media_status.player_is_playing = False
+ player_media_status.player_is_paused = True
+ entity.new_media_status(player_media_status)
+ entity.media_next_track()
+ assert not entity._dynamic_group_cast.media_controller.queue_next.called
+ assert chromecast.media_controller.queue_next.called
+
+ # Player is in unknown state, dynamic group is playing -> Should forward
+ player_media_status.player_state = "UNKNOWN"
+ entity.new_media_status(player_media_status)
+ entity.media_seek(None)
+ assert entity._dynamic_group_cast.media_controller.seek.called
+ assert not chromecast.media_controller.seek.called
+
+ # Verify play_media is not forwarded
+ entity.play_media(None, None)
+ assert not entity._dynamic_group_cast.media_controller.play_media.called
+ assert chromecast.media_controller.play_media.called
+
+
async def test_disconnect_on_stop(hass: HomeAssistantType):
"""Test cast device disconnects socket on stop."""
info = get_fake_chromecast_info()
diff --git a/tests/components/cloud/__init__.py b/tests/components/cloud/__init__.py
index 3a07e52724f..08ab5324b97 100644
--- a/tests/components/cloud/__init__.py
+++ b/tests/components/cloud/__init__.py
@@ -26,7 +26,7 @@ def mock_cloud_prefs(hass, prefs={}):
prefs_to_set = {
const.PREF_ENABLE_ALEXA: True,
const.PREF_ENABLE_GOOGLE: True,
- const.PREF_GOOGLE_ALLOW_UNLOCK: True,
+ const.PREF_GOOGLE_SECURE_DEVICES_PIN: None,
}
prefs_to_set.update(prefs)
hass.data[cloud.DOMAIN].client._prefs._prefs = prefs_to_set
diff --git a/tests/components/cloud/test_binary_sensor.py b/tests/components/cloud/test_binary_sensor.py
index f6d8783a609..ed43e403ef0 100644
--- a/tests/components/cloud/test_binary_sensor.py
+++ b/tests/components/cloud/test_binary_sensor.py
@@ -11,6 +11,15 @@ async def test_remote_connection_sensor(hass):
bin_sensor.WAIT_UNTIL_CHANGE = 0
assert await async_setup_component(hass, 'cloud', {'cloud': {}})
+ await hass.async_block_till_done()
+
+ assert hass.states.get('binary_sensor.remote_ui') is None
+
+ # Fake connection/discovery
+ org_cloud = hass.data['cloud']
+ await org_cloud.iot._on_connect[-1]()
+
+ # Mock test env
cloud = hass.data['cloud'] = Mock()
cloud.remote.certificate = None
await hass.async_block_till_done()
diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py
index 6c50a158cad..4aebc5679a0 100644
--- a/tests/components/cloud/test_http_api.py
+++ b/tests/components/cloud/test_http_api.py
@@ -9,7 +9,8 @@ from hass_nabucasa.const import STATE_CONNECTED
from homeassistant.auth.providers import trusted_networks as tn_auth
from homeassistant.components.cloud.const import (
- PREF_ENABLE_GOOGLE, PREF_ENABLE_ALEXA, PREF_GOOGLE_ALLOW_UNLOCK, DOMAIN)
+ PREF_ENABLE_GOOGLE, PREF_ENABLE_ALEXA, PREF_GOOGLE_SECURE_DEVICES_PIN,
+ DOMAIN)
from tests.common import mock_coro
@@ -335,7 +336,7 @@ async def test_websocket_status(hass, hass_ws_client, mock_cloud_fixture,
client = await hass_ws_client(hass)
with patch.dict(
- 'homeassistant.components.google_assistant.smart_home.'
+ 'homeassistant.components.google_assistant.const.'
'DOMAIN_TO_GOOGLE_TYPES', {'light': None}, clear=True
), patch.dict('homeassistant.components.alexa.smart_home.ENTITY_ADAPTERS',
{'switch': None}, clear=True):
@@ -493,21 +494,21 @@ async def test_websocket_update_preferences(hass, hass_ws_client,
"""Test updating preference."""
assert setup_api[PREF_ENABLE_GOOGLE]
assert setup_api[PREF_ENABLE_ALEXA]
- assert setup_api[PREF_GOOGLE_ALLOW_UNLOCK]
+ assert setup_api[PREF_GOOGLE_SECURE_DEVICES_PIN] is None
client = await hass_ws_client(hass)
await client.send_json({
'id': 5,
'type': 'cloud/update_prefs',
'alexa_enabled': False,
'google_enabled': False,
- 'google_allow_unlock': False,
+ 'google_secure_devices_pin': '1234',
})
response = await client.receive_json()
assert response['success']
assert not setup_api[PREF_ENABLE_GOOGLE]
assert not setup_api[PREF_ENABLE_ALEXA]
- assert not setup_api[PREF_GOOGLE_ALLOW_UNLOCK]
+ assert setup_api[PREF_GOOGLE_SECURE_DEVICES_PIN] == '1234'
async def test_enabling_webhook(hass, hass_ws_client, setup_api,
diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py
index 0de395c8bbc..ea611c29df1 100644
--- a/tests/components/cloud/test_init.py
+++ b/tests/components/cloud/test_init.py
@@ -1,6 +1,10 @@
"""Test the cloud component."""
from unittest.mock import patch
+import pytest
+
+from homeassistant.core import Context
+from homeassistant.exceptions import Unauthorized
from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.components import cloud
from homeassistant.components.cloud.const import DOMAIN
@@ -34,7 +38,7 @@ async def test_constructor_loads_info_from_config(hass):
assert cl.relayer == 'test-relayer'
-async def test_remote_services(hass, mock_cloud_fixture):
+async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user):
"""Setup cloud component and test services."""
cloud = hass.data[DOMAIN]
@@ -58,6 +62,26 @@ async def test_remote_services(hass, mock_cloud_fixture):
assert mock_disconnect.called
assert not cloud.client.remote_autostart
+ # Test admin access required
+ non_admin_context = Context(user_id=hass_read_only_user.id)
+
+ with patch(
+ "hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro()
+ ) as mock_connect, pytest.raises(Unauthorized):
+ await hass.services.async_call(DOMAIN, "remote_connect", blocking=True,
+ context=non_admin_context)
+
+ assert mock_connect.called is False
+
+ with patch(
+ "hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro()
+ ) as mock_disconnect, pytest.raises(Unauthorized):
+ await hass.services.async_call(
+ DOMAIN, "remote_disconnect", blocking=True,
+ context=non_admin_context)
+
+ assert mock_disconnect.called is False
+
async def test_startup_shutdown_events(hass, mock_cloud_fixture):
"""Test if the cloud will start on startup event."""
diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py
index 6d2304433ab..1b5a40ade8a 100644
--- a/tests/components/config/test_config_entries.py
+++ b/tests/components/config/test_config_entries.py
@@ -12,15 +12,15 @@ from homeassistant.config_entries import HANDLERS
from homeassistant.core import callback
from homeassistant.setup import async_setup_component
from homeassistant.components.config import config_entries
-from homeassistant.loader import set_component
-from tests.common import MockConfigEntry, MockModule, mock_coro_func
+from tests.common import (
+ MockConfigEntry, MockModule, mock_coro_func, mock_integration)
@pytest.fixture(autouse=True)
def mock_test_component(hass):
"""Ensure a component called 'test' exists."""
- set_component(hass, 'test', MockModule('test'))
+ mock_integration(hass, MockModule('test'))
@pytest.fixture
@@ -244,8 +244,8 @@ def test_abort(hass, client):
@asyncio.coroutine
def test_create_account(hass, client):
"""Test a flow that creates an account."""
- set_component(
- hass, 'test',
+ mock_integration(
+ hass,
MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow):
@@ -283,8 +283,8 @@ def test_create_account(hass, client):
@asyncio.coroutine
def test_two_step_flow(hass, client):
"""Test we can finish a two step flow."""
- set_component(
- hass, 'test',
+ mock_integration(
+ hass,
MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow):
@@ -349,8 +349,8 @@ def test_two_step_flow(hass, client):
async def test_continue_flow_unauth(hass, client, hass_admin_user):
"""Test we can't finish a two step flow."""
- set_component(
- hass, 'test',
+ mock_integration(
+ hass,
MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow):
@@ -562,8 +562,8 @@ async def test_options_flow(hass, client):
async def test_two_step_options_flow(hass, client):
"""Test we can finish a two step options flow."""
- set_component(
- hass, 'test',
+ mock_integration(
+ hass,
MockModule('test', async_setup_entry=mock_coro_func(True)))
class TestFlow(core_ce.ConfigFlow):
diff --git a/tests/components/conftest.py b/tests/components/conftest.py
index 4903e8c6455..c8ae648e0a1 100644
--- a/tests/components/conftest.py
+++ b/tests/components/conftest.py
@@ -24,7 +24,7 @@ def hass_ws_client(aiohttp_client, hass_access_token):
"""Websocket client fixture connected to websocket server."""
async def create_client(hass, access_token=hass_access_token):
"""Create a websocket client."""
- assert await async_setup_component(hass, 'websocket_api')
+ assert await async_setup_component(hass, 'websocket_api', {})
client = await aiohttp_client(hass.http.app)
diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py
index ba39afa0e88..1aee53f43c2 100644
--- a/tests/components/deconz/test_binary_sensor.py
+++ b/tests/components/deconz/test_binary_sensor.py
@@ -54,7 +54,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -64,6 +64,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'binary_sensor')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -79,56 +80,56 @@ async def test_platform_manually_configured(hass):
async def test_no_binary_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities."""
data = {}
- await setup_gateway(hass, data)
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, data)
+ assert len(hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_binary_sensors(hass):
"""Test successful creation of binary sensor entities."""
data = {"sensors": SENSOR}
- await setup_gateway(hass, data)
- assert "binary_sensor.sensor_1_name" in \
- hass.data[deconz.DOMAIN].deconz_ids
- assert "binary_sensor.sensor_2_name" not in \
- hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, data)
+ assert "binary_sensor.sensor_1_name" in gateway.deconz_ids
+ assert "binary_sensor.sensor_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 1
- hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
+ hass.data[deconz.DOMAIN][gateway.bridgeid].api.sensors['1'].async_update(
{'state': {'on': False}})
async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities."""
data = {}
- await setup_gateway(hass, data)
+ gateway = await setup_gateway(hass, data)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'ZHAPresence'
sensor.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
- assert "binary_sensor.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "binary_sensor.name" in gateway.deconz_ids
async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored."""
data = {}
- await setup_gateway(hass, data, allow_clip_sensor=False)
+ gateway = await setup_gateway(hass, data, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPPresence'
sensor.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ assert len(gateway.deconz_ids) == 0
async def test_unload_switch(hass):
"""Test that it works to unload switch entities."""
data = {"sensors": SENSOR}
- await setup_gateway(hass, data)
+ gateway = await setup_gateway(hass, data)
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
assert len(hass.states.async_all()) == 0
diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py
index 953bb776419..a5f4d2bb79b 100644
--- a/tests/components/deconz/test_climate.py
+++ b/tests/components/deconz/test_climate.py
@@ -65,7 +65,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(hass.loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -75,6 +75,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'climate')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -89,26 +90,26 @@ async def test_platform_manually_configured(hass):
async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no climate entities."""
- await setup_gateway(hass, {})
- assert not hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {})
+ assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert not hass.states.async_all()
async def test_climate_devices(hass):
"""Test successful creation of sensor entities."""
- await setup_gateway(hass, {"sensors": SENSOR})
- assert "climate.climate_1_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {"sensors": SENSOR})
+ assert "climate.climate_1_name" in gateway.deconz_ids
+ assert "sensor.sensor_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 1
- hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
+ gateway.api.sensors['1'].async_update(
{'state': {'on': False}})
await hass.services.async_call(
'climate', 'turn_on', {'entity_id': 'climate.climate_1_name'},
blocking=True
)
- hass.data[deconz.DOMAIN].api.session.put.assert_called_with(
+ gateway.api.session.put.assert_called_with(
'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"mode": "auto"}'
)
@@ -117,7 +118,7 @@ async def test_climate_devices(hass):
'climate', 'turn_off', {'entity_id': 'climate.climate_1_name'},
blocking=True
)
- hass.data[deconz.DOMAIN].api.session.put.assert_called_with(
+ gateway.api.session.put.assert_called_with(
'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"mode": "off"}'
)
@@ -127,18 +128,18 @@ async def test_climate_devices(hass):
{'entity_id': 'climate.climate_1_name', 'temperature': 20},
blocking=True
)
- hass.data[deconz.DOMAIN].api.session.put.assert_called_with(
+ gateway.api.session.put.assert_called_with(
'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"heatsetpoint": 2000.0}'
)
- assert len(hass.data[deconz.DOMAIN].api.session.put.mock_calls) == 3
+ assert len(gateway.api.session.put.mock_calls) == 3
async def test_verify_state_update(hass):
"""Test that state update properly."""
- await setup_gateway(hass, {"sensors": SENSOR})
- assert "climate.climate_1_name" in hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {"sensors": SENSOR})
+ assert "climate.climate_1_name" in gateway.deconz_ids
thermostat = hass.states.get('climate.climate_1_name')
assert thermostat.state == 'on'
@@ -150,7 +151,7 @@ async def test_verify_state_update(hass):
"id": "1",
"config": {"on": False}
}
- hass.data[deconz.DOMAIN].api.async_event_handler(state_update)
+ gateway.api.async_event_handler(state_update)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
@@ -161,32 +162,34 @@ async def test_verify_state_update(hass):
async def test_add_new_climate_device(hass):
"""Test successful creation of climate entities."""
- await setup_gateway(hass, {})
+ gateway = await setup_gateway(hass, {})
sensor = Mock()
sensor.name = 'name'
sensor.type = 'ZHAThermostat'
sensor.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
- assert "climate.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "climate.name" in gateway.deconz_ids
async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored."""
- await setup_gateway(hass, {}, allow_clip_sensor=False)
+ gateway = await setup_gateway(hass, {}, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPThermostat'
sensor.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ assert len(gateway.deconz_ids) == 0
async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities."""
- await setup_gateway(hass, {"sensors": SENSOR})
+ gateway = await setup_gateway(hass, {"sensors": SENSOR})
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
assert len(hass.states.async_all()) == 0
diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py
index 863e4e93fc5..ada506be428 100644
--- a/tests/components/deconz/test_config_flow.py
+++ b/tests/components/deconz/test_config_flow.py
@@ -18,67 +18,57 @@ async def test_flow_works(hass, aioclient_mock):
{"success": {"username": "1234567890ABCDEF"}}
], headers={'content-type': 'application/json'})
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ context={'source': 'user'}
+ )
- await flow.async_step_user()
- await flow.async_step_link(user_input={})
+ assert result['type'] == 'form'
+ assert result['step_id'] == 'link'
- result = await flow.async_step_options(
- user_input={'allow_clip_sensor': True, 'allow_deconz_groups': True})
+ result = await hass.config_entries.flow.async_configure(
+ result['flow_id'],
+ user_input={}
+ )
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
- 'bridgeid': 'id',
- 'host': '1.2.3.4',
- 'port': 80,
- 'api_key': '1234567890ABCDEF',
- 'allow_clip_sensor': True,
- 'allow_deconz_groups': True
+ config_flow.CONF_BRIDGEID: 'id',
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
}
-async def test_flow_already_registered_bridge(hass):
- """Test config flow don't allow more than one bridge to be registered."""
- MockConfigEntry(domain='deconz', data={
- 'host': '1.2.3.4'
- }).add_to_hass(hass)
-
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
-
- result = await flow.async_step_user()
- assert result['type'] == 'abort'
-
-
-async def test_flow_bridge_discovery_fails(hass, aioclient_mock):
+async def test_user_step_bridge_discovery_fails(hass, aioclient_mock):
"""Test config flow works when discovery fails."""
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
-
with patch('pydeconz.utils.async_discovery',
side_effect=asyncio.TimeoutError):
- result = await flow.async_step_user()
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ context={'source': 'user'}
+ )
assert result['type'] == 'form'
assert result['step_id'] == 'init'
-async def test_flow_no_discovered_bridges(hass, aioclient_mock):
+async def test_user_step_no_discovered_bridges(hass, aioclient_mock):
"""Test config flow discovers no bridges."""
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[],
headers={'content-type': 'application/json'})
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ context={'source': 'user'}
+ )
- result = await flow.async_step_user()
assert result['type'] == 'form'
assert result['step_id'] == 'init'
-async def test_flow_one_bridge_discovered(hass, aioclient_mock):
+async def test_user_step_one_bridge_discovered(hass, aioclient_mock):
"""Test config flow discovers one bridge."""
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': 80}
@@ -88,61 +78,85 @@ async def test_flow_one_bridge_discovered(hass, aioclient_mock):
flow.hass = hass
result = await flow.async_step_user()
+
assert result['type'] == 'form'
assert result['step_id'] == 'link'
- assert flow.deconz_config['host'] == '1.2.3.4'
+ assert flow.deconz_config[config_flow.CONF_HOST] == '1.2.3.4'
-async def test_flow_two_bridges_discovered(hass, aioclient_mock):
+async def test_user_step_two_bridges_discovered(hass, aioclient_mock):
"""Test config flow discovers two bridges."""
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
{'id': 'id1', 'internalipaddress': '1.2.3.4', 'internalport': 80},
{'id': 'id2', 'internalipaddress': '5.6.7.8', 'internalport': 80}
], headers={'content-type': 'application/json'})
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ context={'source': 'user'}
+ )
- result = await flow.async_step_user()
- assert result['data_schema']({'host': '1.2.3.4'})
- assert result['data_schema']({'host': '5.6.7.8'})
+ assert result['data_schema']({config_flow.CONF_HOST: '1.2.3.4'})
+ assert result['data_schema']({config_flow.CONF_HOST: '5.6.7.8'})
-async def test_flow_two_bridges_selection(hass, aioclient_mock):
+async def test_user_step_two_bridges_selection(hass, aioclient_mock):
"""Test config flow selection of one of two bridges."""
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
flow.bridges = [
- {'bridgeid': 'id1', 'host': '1.2.3.4', 'port': 80},
- {'bridgeid': 'id2', 'host': '5.6.7.8', 'port': 80}
+ {
+ config_flow.CONF_BRIDGEID: 'id1',
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80
+ },
+ {
+ config_flow.CONF_BRIDGEID: 'id2',
+ config_flow.CONF_HOST: '5.6.7.8',
+ config_flow.CONF_PORT: 80
+ }
]
- result = await flow.async_step_user(user_input={'host': '1.2.3.4'})
+ result = await flow.async_step_user(
+ user_input={config_flow.CONF_HOST: '1.2.3.4'})
assert result['type'] == 'form'
assert result['step_id'] == 'link'
- assert flow.deconz_config['host'] == '1.2.3.4'
+ assert flow.deconz_config[config_flow.CONF_HOST] == '1.2.3.4'
-async def test_flow_manual_configuration(hass, aioclient_mock):
+async def test_user_step_manual_configuration(hass, aioclient_mock):
"""Test config flow with manual input."""
- aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[])
+ aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[],
+ headers={'content-type': 'application/json'})
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ context={'source': 'user'}
+ )
- user_input = {'host': '1.2.3.4', 'port': 80}
+ assert result['type'] == 'form'
+ assert result['step_id'] == 'init'
+
+ result = await hass.config_entries.flow.async_configure(
+ result['flow_id'],
+ user_input={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80
+ }
+ )
- result = await flow.async_step_user(user_input)
assert result['type'] == 'form'
assert result['step_id'] == 'link'
- assert flow.deconz_config == user_input
async def test_link_no_api_key(hass):
"""Test config flow should abort if no API key was possible to retrieve."""
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
- flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
+ flow.deconz_config = {
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80
+ }
with patch('pydeconz.utils.async_get_api_key',
side_effect=pydeconz.errors.ResponseError):
@@ -153,64 +167,50 @@ async def test_link_no_api_key(hass):
assert result['errors'] == {'base': 'no_key'}
-async def test_link_already_registered_bridge(hass):
- """Test that link verifies to only allow one config entry to complete.
-
- This is possible with discovery which will allow the user to complete
- a second config entry and then complete the discovered config entry.
- """
- MockConfigEntry(domain='deconz', data={
- 'host': '1.2.3.4'
- }).add_to_hass(hass)
-
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
- flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
-
- result = await flow.async_step_link(user_input={})
- assert result['type'] == 'abort'
-
-
async def test_bridge_discovery(hass):
"""Test a bridge being discovered."""
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
-
- result = await flow.async_step_discovery({
- 'host': '1.2.3.4',
- 'port': 80,
- 'serial': 'id'
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_SERIAL: 'id',
+ },
+ context={'source': 'discovery'}
+ )
assert result['type'] == 'form'
assert result['step_id'] == 'link'
-async def test_bridge_discovery_already_configured(hass):
+async def test_bridge_discovery_update_existing_entry(hass):
"""Test if a discovered bridge has already been configured."""
- MockConfigEntry(domain='deconz', data={
- 'host': '1.2.3.4'
- }).add_to_hass(hass)
-
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
-
- result = await flow.async_step_discovery({
- 'host': '1.2.3.4',
- 'serial': 'id'
+ entry = MockConfigEntry(domain=config_flow.DOMAIN, data={
+ config_flow.CONF_HOST: '1.2.3.4', config_flow.CONF_BRIDGEID: 'id'
})
+ entry.add_to_hass(hass)
+
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: 'mock-deconz',
+ config_flow.CONF_SERIAL: 'id',
+ },
+ context={'source': 'discovery'}
+ )
assert result['type'] == 'abort'
+ assert result['reason'] == 'updated_instance'
+ assert entry.data[config_flow.CONF_HOST] == 'mock-deconz'
async def test_import_without_api_key(hass):
"""Test importing a host without an API key."""
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
-
- result = await flow.async_step_import({
- 'host': '1.2.3.4',
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={config_flow.CONF_HOST: '1.2.3.4'},
+ context={'source': 'import'}
+ )
assert result['type'] == 'form'
assert result['step_id'] == 'link'
@@ -218,99 +218,119 @@ async def test_import_without_api_key(hass):
async def test_import_with_api_key(hass):
"""Test importing a host with an API key."""
- flow = config_flow.DeconzFlowHandler()
- flow.hass = hass
-
- result = await flow.async_step_import({
- 'bridgeid': 'id',
- 'host': '1.2.3.4',
- 'port': 80,
- 'api_key': '1234567890ABCDEF'
- })
+ result = await hass.config_entries.flow.async_init(
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_BRIDGEID: 'id',
+ config_flow.CONF_HOST: 'mock-deconz',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
+ },
+ context={'source': 'import'}
+ )
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
- 'bridgeid': 'id',
- 'host': '1.2.3.4',
- 'port': 80,
- 'api_key': '1234567890ABCDEF',
- 'allow_clip_sensor': True,
- 'allow_deconz_groups': True
+ config_flow.CONF_BRIDGEID: 'id',
+ config_flow.CONF_HOST: 'mock-deconz',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
}
-async def test_options(hass, aioclient_mock):
- """Test that options work and that bridgeid can be requested."""
+async def test_create_entry(hass, aioclient_mock):
+ """Test that _create_entry work and that bridgeid can be requested."""
aioclient_mock.get('http://1.2.3.4:80/api/1234567890ABCDEF/config',
json={"bridgeid": "id"},
headers={'content-type': 'application/json'})
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
- flow.deconz_config = {'host': '1.2.3.4',
- 'port': 80,
- 'api_key': '1234567890ABCDEF'}
+ flow.deconz_config = {
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
+ }
- result = await flow.async_step_options(
- user_input={'allow_clip_sensor': False, 'allow_deconz_groups': False})
+ result = await flow._create_entry()
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
- 'bridgeid': 'id',
- 'host': '1.2.3.4',
- 'port': 80,
- 'api_key': '1234567890ABCDEF',
- 'allow_clip_sensor': False,
- 'allow_deconz_groups': False
+ config_flow.CONF_BRIDGEID: 'id',
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
}
-async def test_hassio_single_instance(hass):
- """Test we only allow a single config flow."""
- MockConfigEntry(domain='deconz', data={
- 'host': '1.2.3.4'
- }).add_to_hass(hass)
+async def test_create_entry_timeout(hass, aioclient_mock):
+ """Test that _create_entry handles a timeout."""
+ flow = config_flow.DeconzFlowHandler()
+ flow.hass = hass
+ flow.deconz_config = {
+ config_flow.CONF_HOST: '1.2.3.4',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
+ }
+
+ with patch('pydeconz.utils.async_get_bridgeid',
+ side_effect=asyncio.TimeoutError):
+ result = await flow._create_entry()
+
+ assert result['type'] == 'abort'
+ assert result['reason'] == 'no_bridges'
+
+
+async def test_hassio_update_instance(hass):
+ """Test we can update an existing config entry."""
+ entry = MockConfigEntry(domain=config_flow.DOMAIN, data={
+ config_flow.CONF_BRIDGEID: 'id',
+ config_flow.CONF_HOST: '1.2.3.4'
+ })
+ entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
- 'deconz', context={'source': 'hassio'})
+ config_flow.DOMAIN,
+ data={
+ config_flow.CONF_HOST: 'mock-deconz',
+ config_flow.CONF_SERIAL: 'id'
+ },
+ context={'source': 'hassio'}
+ )
+
assert result['type'] == 'abort'
- assert result['reason'] == 'one_instance_only'
+ assert result['reason'] == 'updated_instance'
+ assert entry.data[config_flow.CONF_HOST] == 'mock-deconz'
async def test_hassio_confirm(hass):
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
- 'deconz',
+ config_flow.DOMAIN,
data={
'addon': 'Mock Addon',
- 'host': 'mock-deconz',
- 'port': 8080,
- 'serial': 'aa:bb',
- 'api_key': '1234567890ABCDEF',
+ config_flow.CONF_HOST: 'mock-deconz',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_SERIAL: 'id',
+ config_flow.CONF_API_KEY: '1234567890ABCDEF',
},
context={'source': 'hassio'}
)
assert result['type'] == 'form'
assert result['step_id'] == 'hassio_confirm'
- assert result['description_placeholders'] == {
- 'addon': 'Mock Addon',
- }
+ assert result['description_placeholders'] == {'addon': 'Mock Addon'}
result = await hass.config_entries.flow.async_configure(
- result['flow_id'], {
- 'allow_clip_sensor': True,
- 'allow_deconz_groups': True,
- }
+ result['flow_id'],
+ user_input={}
)
assert result['type'] == 'create_entry'
assert result['result'].data == {
- 'host': 'mock-deconz',
- 'port': 8080,
- 'bridgeid': 'aa:bb',
- 'api_key': '1234567890ABCDEF',
- 'allow_clip_sensor': True,
- 'allow_deconz_groups': True,
+ config_flow.CONF_HOST: 'mock-deconz',
+ config_flow.CONF_PORT: 80,
+ config_flow.CONF_BRIDGEID: 'id',
+ config_flow.CONF_API_KEY: '1234567890ABCDEF'
}
diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py
index b021bcb8d51..73e3d411958 100644
--- a/tests/components/deconz/test_cover.py
+++ b/tests/components/deconz/test_cover.py
@@ -61,7 +61,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -70,6 +70,7 @@ async def setup_gateway(hass, data):
await hass.config_entries.async_forward_entry_setup(config_entry, 'cover')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -84,8 +85,8 @@ async def test_platform_manually_configured(hass):
async def test_no_covers(hass):
"""Test that no cover entities are created."""
- await setup_gateway(hass, {})
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, {})
+ assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@@ -93,8 +94,8 @@ async def test_cover(hass):
"""Test that all supported cover entities are created."""
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
- await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
- assert "cover.cover_1_name" in hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
+ assert "cover.cover_1_name" in gateway.deconz_ids
assert len(SUPPORTED_COVERS) == len(COVER_TYPES)
assert len(hass.states.async_all()) == 3
@@ -102,7 +103,7 @@ async def test_cover(hass):
assert cover_1 is not None
assert cover_1.state == 'closed'
- hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
+ gateway.api.lights['1'].async_update({})
await hass.services.async_call('cover', 'open_cover', {
'entity_id': 'cover.cover_1_name'
@@ -122,14 +123,15 @@ async def test_cover(hass):
async def test_add_new_cover(hass):
"""Test successful creation of cover entity."""
data = {}
- await setup_gateway(hass, data)
+ gateway = await setup_gateway(hass, data)
cover = Mock()
cover.name = 'name'
cover.type = "Level controllable output"
cover.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_light', [cover])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('light'), [cover])
await hass.async_block_till_done()
- assert "cover.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "cover.name" in gateway.deconz_ids
async def test_unsupported_cover(hass):
@@ -140,8 +142,8 @@ async def test_unsupported_cover(hass):
async def test_unload_cover(hass):
"""Test that it works to unload switch entities."""
- await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
+ gateway = await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
assert len(hass.states.async_all()) == 1
diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py
index e0afadccc81..b844cf4336e 100644
--- a/tests/components/deconz/test_init.py
+++ b/tests/components/deconz/test_init.py
@@ -11,56 +11,62 @@ from homeassistant.components import deconz
from tests.common import mock_coro, MockConfigEntry
+ENTRY1_HOST = '1.2.3.4'
+ENTRY1_PORT = 80
+ENTRY1_API_KEY = '1234567890ABCDEF'
+ENTRY1_BRIDGEID = '12345ABC'
-CONFIG = {
- "config": {
- "bridgeid": "0123456789ABCDEF",
- "mac": "12:34:56:78:90:ab",
- "modelid": "deCONZ",
- "name": "Phoscon",
- "swversion": "2.05.35"
- }
-}
+ENTRY2_HOST = '2.3.4.5'
+ENTRY2_PORT = 80
+ENTRY2_API_KEY = '1234567890ABCDEF'
+ENTRY2_BRIDGEID = '23456DEF'
+
+
+async def setup_entry(hass, entry):
+ """Test that setup entry works."""
+ with patch.object(deconz.DeconzGateway, 'async_setup',
+ return_value=mock_coro(True)), \
+ patch.object(deconz.DeconzGateway, 'async_update_device_registry',
+ return_value=mock_coro(True)):
+ assert await deconz.async_setup_entry(hass, entry) is True
async def test_config_with_host_passed_to_config_entry(hass):
"""Test that configured options for a host are loaded via config entry."""
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(deconz, 'configured_hosts', return_value=[]):
+ with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {
- deconz.CONF_HOST: '1.2.3.4',
- deconz.CONF_PORT: 80
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT
}
}) is True
# Import flow started
- assert len(mock_config_entries.flow.mock_calls) == 2
+ assert len(mock_config_flow.mock_calls) == 1
async def test_config_without_host_not_passed_to_config_entry(hass):
"""Test that a configuration without a host does not initiate an import."""
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(deconz, 'configured_hosts', return_value=[]):
+ MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
+ with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {}
}) is True
# No flow started
- assert len(mock_config_entries.flow.mock_calls) == 0
+ assert len(mock_config_flow.mock_calls) == 0
-async def test_config_already_registered_not_passed_to_config_entry(hass):
+async def test_config_import_entry_fails_when_entries_exist(hass):
"""Test that an already registered host does not initiate an import."""
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(deconz, 'configured_hosts',
- return_value=['1.2.3.4']):
+ MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
+ with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {
- deconz.CONF_HOST: '1.2.3.4',
- deconz.CONF_PORT: 80
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT
}
}) is True
# No flow started
- assert len(mock_config_entries.flow.mock_calls) == 0
+ assert len(mock_config_flow.mock_calls) == 0
async def test_config_discovery(hass):
@@ -71,16 +77,14 @@ async def test_config_discovery(hass):
assert len(mock_config_entries.flow.mock_calls) == 0
-async def test_setup_entry_already_registered_bridge(hass):
- """Test setup entry doesn't allow more than one instance of deCONZ."""
- hass.data[deconz.DOMAIN] = True
- assert await deconz.async_setup_entry(hass, {}) is False
-
-
async def test_setup_entry_fails(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
- entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
+ entry.data = {
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY
+ }
with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=Exception):
await deconz.async_setup_entry(hass, entry)
@@ -89,61 +93,121 @@ async def test_setup_entry_fails(hass):
async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
- entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
- with patch(
- 'pydeconz.DeconzSession.async_load_parameters',
- side_effect=asyncio.TimeoutError
- ), pytest.raises(ConfigEntryNotReady):
+ entry.data = {
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY
+ }
+ with patch('pydeconz.DeconzSession.async_load_parameters',
+ side_effect=asyncio.TimeoutError),\
+ pytest.raises(ConfigEntryNotReady):
await deconz.async_setup_entry(hass, entry)
async def test_setup_entry_successful(hass):
"""Test setup entry is successful."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
- 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
- mock_registry = Mock()
- with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
- patch('homeassistant.helpers.device_registry.async_get_registry',
- return_value=mock_coro(mock_registry)):
- mock_gateway.return_value.async_setup.return_value = mock_coro(True)
- assert await deconz.async_setup_entry(hass, entry) is True
- assert hass.data[deconz.DOMAIN]
+
+ await setup_entry(hass, entry)
+
+ assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
+ assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
+
+
+async def test_setup_entry_multiple_gateways(hass):
+ """Test setup entry is successful with multiple gateways."""
+ entry = MockConfigEntry(domain=deconz.DOMAIN, data={
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
+ })
+ entry.add_to_hass(hass)
+
+ entry2 = MockConfigEntry(domain=deconz.DOMAIN, data={
+ deconz.CONF_HOST: ENTRY2_HOST,
+ deconz.CONF_PORT: ENTRY2_PORT,
+ deconz.CONF_API_KEY: ENTRY2_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID
+ })
+ entry2.add_to_hass(hass)
+
+ await setup_entry(hass, entry)
+ await setup_entry(hass, entry2)
+
+ assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
+ assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
+ assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN]
+ assert not hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master
async def test_unload_entry(hass):
"""Test being able to unload an entry."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
- 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
- mock_registry = Mock()
- with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
- patch('homeassistant.helpers.device_registry.async_get_registry',
- return_value=mock_coro(mock_registry)):
- mock_gateway.return_value.async_setup.return_value = mock_coro(True)
- assert await deconz.async_setup_entry(hass, entry) is True
- mock_gateway.return_value.async_reset.return_value = mock_coro(True)
- assert await deconz.async_unload_entry(hass, entry)
- assert deconz.DOMAIN not in hass.data
+ await setup_entry(hass, entry)
+
+ with patch.object(deconz.DeconzGateway, 'async_reset',
+ return_value=mock_coro(True)):
+ assert await deconz.async_unload_entry(hass, entry)
+
+ assert not hass.data[deconz.DOMAIN]
+
+
+async def test_unload_entry_multiple_gateways(hass):
+ """Test being able to unload an entry and master gateway gets moved."""
+ entry = MockConfigEntry(domain=deconz.DOMAIN, data={
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
+ })
+ entry.add_to_hass(hass)
+
+ entry2 = MockConfigEntry(domain=deconz.DOMAIN, data={
+ deconz.CONF_HOST: ENTRY2_HOST,
+ deconz.CONF_PORT: ENTRY2_PORT,
+ deconz.CONF_API_KEY: ENTRY2_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID
+ })
+ entry2.add_to_hass(hass)
+
+ await setup_entry(hass, entry)
+ await setup_entry(hass, entry2)
+
+ with patch.object(deconz.DeconzGateway, 'async_reset',
+ return_value=mock_coro(True)):
+ assert await deconz.async_unload_entry(hass, entry)
+
+ assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN]
+ assert hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master
async def test_service_configure(hass):
"""Test that service invokes pydeconz with the correct path and data."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
- 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
- mock_registry = Mock()
- with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
- patch('homeassistant.helpers.device_registry.async_get_registry',
- return_value=mock_coro(mock_registry)):
- mock_gateway.return_value.async_setup.return_value = mock_coro(True)
- assert await deconz.async_setup_entry(hass, entry) is True
- hass.data[deconz.DOMAIN].deconz_ids = {
+ await setup_entry(hass, entry)
+
+ hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].deconz_ids = {
'light.test': '/light/1'
}
data = {'on': True, 'attr1': 10, 'attr2': 20}
@@ -191,25 +255,23 @@ async def test_service_configure(hass):
async def test_service_refresh_devices(hass):
"""Test that service can refresh devices."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
- 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
+ deconz.CONF_HOST: ENTRY1_HOST,
+ deconz.CONF_PORT: ENTRY1_PORT,
+ deconz.CONF_API_KEY: ENTRY1_API_KEY,
+ deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
- mock_registry = Mock()
- with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
- patch('homeassistant.helpers.device_registry.async_get_registry',
- return_value=mock_coro(mock_registry)):
- mock_gateway.return_value.async_setup.return_value = mock_coro(True)
- assert await deconz.async_setup_entry(hass, entry) is True
+ await setup_entry(hass, entry)
- with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
- return_value=mock_coro(True)):
+ with patch('pydeconz.DeconzSession.async_load_parameters',
+ return_value=mock_coro(True)):
await hass.services.async_call(
'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done()
- with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
- return_value=mock_coro(False)):
+ with patch('pydeconz.DeconzSession.async_load_parameters',
+ return_value=mock_coro(False)):
await hass.services.async_call(
'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done()
diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py
index 49c3f280d8a..d9f6927fe2c 100644
--- a/tests/components/deconz/test_light.py
+++ b/tests/components/deconz/test_light.py
@@ -87,7 +87,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -96,6 +96,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
await hass.config_entries.async_forward_entry_setup(config_entry, 'light')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -110,8 +111,8 @@ async def test_platform_manually_configured(hass):
async def test_no_lights_or_groups(hass):
"""Test that no lights or groups entities are created."""
- await setup_gateway(hass, {})
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, {})
+ assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@@ -119,11 +120,12 @@ async def test_lights_and_groups(hass):
"""Test that lights or groups entities are created."""
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
- await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP})
- assert "light.light_1_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "light.light_2_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "light.group_1_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "light.group_2_name" not in hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(
+ hass, {"lights": LIGHT, "groups": GROUP})
+ assert "light.light_1_name" in gateway.deconz_ids
+ assert "light.light_2_name" in gateway.deconz_ids
+ assert "light.group_1_name" in gateway.deconz_ids
+ assert "light.group_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 4
lamp_1 = hass.states.get('light.light_1_name')
@@ -137,7 +139,7 @@ async def test_lights_and_groups(hass):
assert light_2.state == 'on'
assert light_2.attributes['color_temp'] == 2500
- hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
+ gateway.api.lights['1'].async_update({})
await hass.services.async_call('light', 'turn_on', {
'entity_id': 'light.light_1_name',
@@ -166,49 +168,52 @@ async def test_lights_and_groups(hass):
async def test_add_new_light(hass):
"""Test successful creation of light entities."""
- await setup_gateway(hass, {})
+ gateway = await setup_gateway(hass, {})
light = Mock()
light.name = 'name'
light.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_light', [light])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('light'), [light])
await hass.async_block_till_done()
- assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "light.name" in gateway.deconz_ids
async def test_add_new_group(hass):
"""Test successful creation of group entities."""
- await setup_gateway(hass, {})
+ gateway = await setup_gateway(hass, {})
group = Mock()
group.name = 'name'
group.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_group', [group])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('group'), [group])
await hass.async_block_till_done()
- assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "light.name" in gateway.deconz_ids
async def test_do_not_add_deconz_groups(hass):
"""Test that clip sensors can be ignored."""
- await setup_gateway(hass, {}, allow_deconz_groups=False)
+ gateway = await setup_gateway(hass, {}, allow_deconz_groups=False)
group = Mock()
group.name = 'name'
group.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_group', [group])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('group'), [group])
await hass.async_block_till_done()
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ assert len(gateway.deconz_ids) == 0
async def test_no_switch(hass):
"""Test that a switch doesn't get created as a light entity."""
- await setup_gateway(hass, {"lights": SWITCH})
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, {"lights": SWITCH})
+ assert len(gateway.deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_unload_light(hass):
"""Test that it works to unload switch entities."""
- await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP})
+ gateway = await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP})
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
# Group.all_lights will not be removed
assert len(hass.states.async_all()) == 1
diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py
index 963f1064b35..0feac24f22a 100644
--- a/tests/components/deconz/test_scene.py
+++ b/tests/components/deconz/test_scene.py
@@ -47,7 +47,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -56,6 +56,7 @@ async def setup_gateway(hass, data):
await hass.config_entries.async_forward_entry_setup(config_entry, 'scene')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -70,8 +71,8 @@ async def test_platform_manually_configured(hass):
async def test_no_scenes(hass):
"""Test that scenes can be loaded without scenes being available."""
- await setup_gateway(hass, {})
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, {})
+ assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@@ -79,8 +80,8 @@ async def test_scenes(hass):
"""Test that scenes works."""
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
- await setup_gateway(hass, {"groups": GROUP})
- assert "scene.group_1_name_scene_1" in hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {"groups": GROUP})
+ assert "scene.group_1_name_scene_1" in gateway.deconz_ids
assert len(hass.states.async_all()) == 1
await hass.services.async_call('scene', 'turn_on', {
@@ -90,8 +91,8 @@ async def test_scenes(hass):
async def test_unload_scene(hass):
"""Test that it works to unload scene entities."""
- await setup_gateway(hass, {"groups": GROUP})
+ gateway = await setup_gateway(hass, {"groups": GROUP})
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
assert len(hass.states.async_all()) == 0
diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py
index f5cfbe2c183..41bb7b362f5 100644
--- a/tests/components/deconz/test_sensor.py
+++ b/tests/components/deconz/test_sensor.py
@@ -91,7 +91,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -101,6 +101,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'sensor')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -115,58 +116,56 @@ async def test_platform_manually_configured(hass):
async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities."""
- await setup_gateway(hass, {})
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, {})
+ assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
async def test_sensors(hass):
"""Test successful creation of sensor entities."""
- await setup_gateway(hass, {"sensors": SENSOR})
- assert "sensor.sensor_1_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids
- assert "sensor.sensor_3_name" not in hass.data[deconz.DOMAIN].deconz_ids
- assert "sensor.sensor_3_name_battery_level" not in \
- hass.data[deconz.DOMAIN].deconz_ids
- assert "sensor.sensor_4_name" not in hass.data[deconz.DOMAIN].deconz_ids
- assert "sensor.sensor_4_name_battery_level" in \
- hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {"sensors": SENSOR})
+ assert "sensor.sensor_1_name" in gateway.deconz_ids
+ assert "sensor.sensor_2_name" not in gateway.deconz_ids
+ assert "sensor.sensor_3_name" not in gateway.deconz_ids
+ assert "sensor.sensor_3_name_battery_level" not in gateway.deconz_ids
+ assert "sensor.sensor_4_name" not in gateway.deconz_ids
+ assert "sensor.sensor_4_name_battery_level" in gateway.deconz_ids
assert len(hass.states.async_all()) == 5
- hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
- {'state': {'on': False}})
- hass.data[deconz.DOMAIN].api.sensors['4'].async_update(
- {'config': {'battery': 75}})
+ gateway.api.sensors['1'].async_update({'state': {'on': False}})
+ gateway.api.sensors['4'].async_update({'config': {'battery': 75}})
async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities."""
- await setup_gateway(hass, {})
+ gateway = await setup_gateway(hass, {})
sensor = Mock()
sensor.name = 'name'
sensor.type = 'ZHATemperature'
sensor.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
- assert "sensor.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "sensor.name" in gateway.deconz_ids
async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored."""
- await setup_gateway(hass, {}, allow_clip_sensor=False)
+ gateway = await setup_gateway(hass, {}, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPTemperature'
sensor.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ assert len(gateway.deconz_ids) == 0
async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities."""
- await setup_gateway(hass, {"sensors": SENSOR})
+ gateway = await setup_gateway(hass, {"sensors": SENSOR})
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
assert len(hass.states.async_all()) == 0
diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py
index 245be27961d..e05362953a1 100644
--- a/tests/components/deconz/test_switch.py
+++ b/tests/components/deconz/test_switch.py
@@ -65,7 +65,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
- hass.data[deconz.DOMAIN] = gateway
+ hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@@ -74,6 +74,7 @@ async def setup_gateway(hass, data):
await hass.config_entries.async_forward_entry_setup(config_entry, 'switch')
# To flush out the service call to update the group
await hass.async_block_till_done()
+ return gateway
async def test_platform_manually_configured(hass):
@@ -88,8 +89,8 @@ async def test_platform_manually_configured(hass):
async def test_no_switches(hass):
"""Test that no switch entities are created."""
- await setup_gateway(hass, {})
- assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
+ gateway = await setup_gateway(hass, {})
+ assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@@ -97,10 +98,10 @@ async def test_switches(hass):
"""Test that all supported switch entities are created."""
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
- await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
- assert "switch.switch_1_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "switch.switch_2_name" in hass.data[deconz.DOMAIN].deconz_ids
- assert "switch.switch_3_name" in hass.data[deconz.DOMAIN].deconz_ids
+ gateway = await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
+ assert "switch.switch_1_name" in gateway.deconz_ids
+ assert "switch.switch_2_name" in gateway.deconz_ids
+ assert "switch.switch_3_name" in gateway.deconz_ids
assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES)
assert len(hass.states.async_all()) == 4
@@ -111,7 +112,7 @@ async def test_switches(hass):
assert switch_3 is not None
assert switch_3.state == 'on'
- hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
+ gateway.api.lights['1'].async_update({})
await hass.services.async_call('switch', 'turn_on', {
'entity_id': 'switch.switch_1_name'
@@ -130,14 +131,15 @@ async def test_switches(hass):
async def test_add_new_switch(hass):
"""Test successful creation of switch entity."""
- await setup_gateway(hass, {})
+ gateway = await setup_gateway(hass, {})
switch = Mock()
switch.name = 'name'
switch.type = "Smart plug"
switch.register_async_callback = Mock()
- async_dispatcher_send(hass, 'deconz_new_light', [switch])
+ async_dispatcher_send(
+ hass, gateway.async_event_new_device('light'), [switch])
await hass.async_block_till_done()
- assert "switch.name" in hass.data[deconz.DOMAIN].deconz_ids
+ assert "switch.name" in gateway.deconz_ids
async def test_unsupported_switch(hass):
@@ -148,8 +150,8 @@ async def test_unsupported_switch(hass):
async def test_unload_switch(hass):
"""Test that it works to unload switch entities."""
- await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
+ gateway = await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
- await hass.data[deconz.DOMAIN].async_reset()
+ await gateway.async_reset()
assert len(hass.states.async_all()) == 1
diff --git a/tests/components/demo/test_geo_location.py b/tests/components/demo/test_geo_location.py
index 5a46ca99839..b72df4d28a0 100644
--- a/tests/components/demo/test_geo_location.py
+++ b/tests/components/demo/test_geo_location.py
@@ -38,29 +38,37 @@ class TestDemoPlatform(unittest.TestCase):
with patch('homeassistant.util.dt.utcnow', return_value=utcnow):
with assert_setup_component(1, geo_location.DOMAIN):
assert setup_component(self.hass, geo_location.DOMAIN, CONFIG)
+ self.hass.block_till_done()
- # In this test, only entities of the geolocation domain have been
+ # In this test, one zone and geolocation entities have been
# generated.
- all_states = self.hass.states.all()
+ all_states = [self.hass.states.get(entity_id) for entity_id
+ in self.hass.states.entity_ids(geo_location.DOMAIN)]
assert len(all_states) == NUMBER_OF_DEMO_DEVICES
- # Check a single device's attributes.
- state_first_entry = all_states[0]
- assert abs(
- state_first_entry.attributes['latitude'] -
- self.hass.config.latitude
- ) < 1.0
- assert abs(
- state_first_entry.attributes['longitude'] -
- self.hass.config.longitude
- ) < 1.0
- assert state_first_entry.attributes['unit_of_measurement'] == \
- DEFAULT_UNIT_OF_MEASUREMENT
+ for state in all_states:
+ # Check a single device's attributes.
+ if state.domain != geo_location.DOMAIN:
+ # ignore home zone state
+ continue
+ assert abs(
+ state.attributes['latitude'] -
+ self.hass.config.latitude
+ ) < 1.0
+ assert abs(
+ state.attributes['longitude'] -
+ self.hass.config.longitude
+ ) < 1.0
+ assert state.attributes['unit_of_measurement'] == \
+ DEFAULT_UNIT_OF_MEASUREMENT
+
# Update (replaces 1 device).
fire_time_changed(self.hass, utcnow + DEFAULT_UPDATE_INTERVAL)
self.hass.block_till_done()
# Get all states again, ensure that the number of states is still
# the same, but the lists are different.
- all_states_updated = self.hass.states.all()
+ all_states_updated = [
+ self.hass.states.get(entity_id) for entity_id
+ in self.hass.states.entity_ids(geo_location.DOMAIN)]
assert len(all_states_updated) == NUMBER_OF_DEMO_DEVICES
assert all_states != all_states_updated
diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py
index b0b2524180f..fde2caecff4 100644
--- a/tests/components/demo/test_init.py
+++ b/tests/components/demo/test_init.py
@@ -1,5 +1,4 @@
"""The tests for the Demo component."""
-import asyncio
import json
import os
@@ -16,18 +15,6 @@ def mock_history(hass):
hass.config.components.add('history')
-@pytest.fixture
-def minimize_demo_platforms(hass):
- """Cleanup demo component for tests."""
- orig = demo.COMPONENTS_WITH_DEMO_PLATFORM
- demo.COMPONENTS_WITH_DEMO_PLATFORM = [
- 'switch', 'light', 'media_player']
-
- yield
-
- demo.COMPONENTS_WITH_DEMO_PLATFORM = orig
-
-
@pytest.fixture(autouse=True)
def demo_cleanup(hass):
"""Clean up device tracker demo file."""
@@ -38,29 +25,15 @@ def demo_cleanup(hass):
pass
-@asyncio.coroutine
-def test_if_demo_state_shows_by_default(hass, minimize_demo_platforms):
- """Test if demo state shows if we give no configuration."""
- yield from async_setup_component(hass, demo.DOMAIN, {demo.DOMAIN: {}})
-
- assert hass.states.get('a.Demo_Mode') is not None
-
-
-@asyncio.coroutine
-def test_hiding_demo_state(hass, minimize_demo_platforms):
- """Test if you can hide the demo card."""
- yield from async_setup_component(hass, demo.DOMAIN, {
- demo.DOMAIN: {'hide_demo_state': 1}})
-
- assert hass.states.get('a.Demo_Mode') is None
-
-
-@asyncio.coroutine
-def test_all_entities_can_be_loaded_over_json(hass):
- """Test if you can hide the demo card."""
- yield from async_setup_component(hass, demo.DOMAIN, {
- demo.DOMAIN: {'hide_demo_state': 1}})
+async def test_setting_up_demo(hass):
+ """Test if we can set up the demo and dump it to JSON."""
+ assert await async_setup_component(hass, demo.DOMAIN, {
+ 'demo': {}
+ })
+ await hass.async_start()
+ # This is done to make sure entity components don't accidentally store
+ # non-JSON-serializable data in the state machine.
try:
json.dumps(hass.states.async_all(), cls=JSONEncoder)
except Exception:
diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py
index 83acf8be601..808e3ee2102 100644
--- a/tests/components/demo/test_media_player.py
+++ b/tests/components/demo/test_media_player.py
@@ -184,9 +184,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(ent_id)
assert 1 == state.attributes.get('media_episode')
- @patch('homeassistant.components.demo.media_player.DemoYoutubePlayer.'
- 'media_seek', autospec=True)
- def test_play_media(self, mock_seek):
+ def test_play_media(self):
"""Test play_media ."""
assert setup_component(
self.hass, mp.DOMAIN,
@@ -212,6 +210,16 @@ class TestDemoMediaPlayer(unittest.TestCase):
state.attributes.get('supported_features'))
assert 'some_id' == state.attributes.get('media_content_id')
+ @patch('homeassistant.components.demo.media_player.DemoYoutubePlayer.'
+ 'media_seek', autospec=True)
+ def test_seek(self, mock_seek):
+ """Test seek."""
+ assert setup_component(
+ self.hass, mp.DOMAIN,
+ {'media_player': {'platform': 'demo'}})
+ ent_id = 'media_player.living_room'
+ state = self.hass.states.get(ent_id)
+ assert state.attributes['supported_features'] & mp.SUPPORT_SEEK
assert not mock_seek.called
with pytest.raises(vol.Invalid):
common.media_seek(self.hass, None, ent_id)
diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py
index 35cf8abe6bd..6e8f45a4d81 100644
--- a/tests/components/demo/test_notify.py
+++ b/tests/components/demo/test_notify.py
@@ -42,9 +42,10 @@ class TestNotifyDemo(unittest.TestCase):
self.hass.stop()
def _setup_notify(self):
- with assert_setup_component(1) as config:
+ with assert_setup_component(1, notify.DOMAIN) as config:
assert setup_component(self.hass, notify.DOMAIN, CONFIG)
assert config[notify.DOMAIN]
+ self.hass.block_till_done()
def test_setup(self):
"""Test setup."""
@@ -73,7 +74,7 @@ class TestNotifyDemo(unittest.TestCase):
self.hass.block_till_done()
assert notify.DOMAIN in self.hass.config.components
assert mock_demo_get_service.called
- assert mock_demo_get_service.call_args[0] == (
+ assert mock_demo_get_service.mock_calls[0][1] == (
self.hass, {}, {'test_key': 'test_val'})
@callback
diff --git a/tests/components/device_sun_light_trigger/test_init.py b/tests/components/device_sun_light_trigger/test_init.py
index f1ef6aa5dd0..634c56ffbad 100644
--- a/tests/components/device_sun_light_trigger/test_init.py
+++ b/tests/components/device_sun_light_trigger/test_init.py
@@ -5,7 +5,6 @@ from asynctest import patch
import pytest
from homeassistant.setup import async_setup_component
-import homeassistant.loader as loader
from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME
from homeassistant.components import (
device_tracker, light, device_sun_light_trigger)
@@ -18,13 +17,13 @@ from tests.components.light import common as common_light
@pytest.fixture
def scanner(hass):
"""Initialize components."""
- scanner = loader.get_component(
- hass, 'device_tracker.test').get_scanner(None, None)
+ scanner = getattr(
+ hass.components, 'test.device_tracker').get_scanner(None, None)
scanner.reset()
scanner.come_home('DEV1')
- loader.get_component(hass, 'light.test').init()
+ getattr(hass.components, 'test.light').init()
with patch(
'homeassistant.components.device_tracker.load_yaml_config_file',
diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py
index 63f1c60327a..89c2c3359ef 100644
--- a/tests/components/device_tracker/test_init.py
+++ b/tests/components/device_tracker/test_init.py
@@ -13,7 +13,6 @@ from homeassistant.components import zone
from homeassistant.core import callback, State
from homeassistant.setup import async_setup_component
from homeassistant.helpers import discovery
-from homeassistant.loader import get_component
import homeassistant.util.dt as dt_util
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN,
@@ -179,10 +178,9 @@ async def test_gravatar_and_picture(hass):
autospec=True)
async def test_discover_platform(mock_demo_setup_scanner, mock_see, hass):
"""Test discovery of device_tracker demo platform."""
- assert device_tracker.DOMAIN not in hass.config.components
await discovery.async_load_platform(
hass, device_tracker.DOMAIN, 'demo', {'test_key': 'test_val'},
- {'demo': {}})
+ {'bla': {}})
await hass.async_block_till_done()
assert device_tracker.DOMAIN in hass.config.components
assert mock_demo_setup_scanner.called
@@ -192,7 +190,7 @@ async def test_discover_platform(mock_demo_setup_scanner, mock_see, hass):
async def test_update_stale(hass):
"""Test stalled update."""
- scanner = get_component(hass, 'device_tracker.test').SCANNER
+ scanner = getattr(hass.components, 'test.device_tracker').SCANNER
scanner.reset()
scanner.come_home('DEV1')
@@ -256,7 +254,7 @@ async def test_device_hidden(hass, yaml_devices):
hide_if_away=True)
device_tracker.update_config(yaml_devices, dev_id, device)
- scanner = get_component(hass, 'device_tracker.test').SCANNER
+ scanner = getattr(hass.components, 'test.device_tracker').SCANNER
scanner.reset()
with assert_setup_component(1, device_tracker.DOMAIN):
@@ -275,7 +273,7 @@ async def test_group_all_devices(hass, yaml_devices):
hide_if_away=True)
device_tracker.update_config(yaml_devices, dev_id, device)
- scanner = get_component(hass, 'device_tracker.test').SCANNER
+ scanner = getattr(hass.components, 'test.device_tracker').SCANNER
scanner.reset()
with assert_setup_component(1, device_tracker.DOMAIN):
@@ -441,7 +439,7 @@ async def test_see_passive_zone_state(hass):
'zone': zone_info
})
- scanner = get_component(hass, 'device_tracker.test').SCANNER
+ scanner = getattr(hass.components, 'test.device_tracker').SCANNER
scanner.reset()
scanner.come_home('dev1')
@@ -557,7 +555,7 @@ def test_bad_platform(hass):
async def test_adding_unknown_device_to_config(mock_device_tracker_conf, hass):
"""Test the adding of unknown devices to configuration file."""
- scanner = get_component(hass, 'device_tracker.test').SCANNER
+ scanner = getattr(hass.components, 'test.device_tracker').SCANNER
scanner.reset()
scanner.come_home('DEV1')
diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py
index 43ce6344ec4..778b3bdad49 100644
--- a/tests/components/dyson/test_climate.py
+++ b/tests/components/dyson/test_climate.py
@@ -2,12 +2,13 @@
import unittest
from unittest import mock
-from libpurecoollink.const import (FocusMode, HeatMode, HeatState, HeatTarget,
- TiltState)
-from libpurecoollink.dyson_pure_state import DysonPureHotCoolState
-from libpurecoollink.dyson_pure_hotcool_link import DysonPureHotCoolLink
-from homeassistant.components.dyson import climate as dyson
+from libpurecool.const import (FocusMode, HeatMode,
+ HeatState, HeatTarget, TiltState)
+from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink
+from libpurecool.dyson_pure_state import DysonPureHotCoolState
+
from homeassistant.components import dyson as dyson_parent
+from homeassistant.components.dyson import climate as dyson
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.setup import setup_component
from tests.common import get_test_home_assistant
@@ -110,9 +111,9 @@ class DysonTest(unittest.TestCase):
"""Stop everything that was started."""
self.hass.stop()
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_device_heat_on(), _get_device_cool()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_setup_component_with_parent_discovery(self, mocked_login,
mocked_devices):
"""Test setup_component using discovery."""
diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py
index a04116f10f2..0a9469ae807 100644
--- a/tests/components/dyson/test_fan.py
+++ b/tests/components/dyson/test_fan.py
@@ -1,16 +1,28 @@
"""Test the Dyson fan component."""
+import json
import unittest
from unittest import mock
-from homeassistant.setup import setup_component
+import asynctest
+from libpurecool.const import FanSpeed, FanMode, NightMode, Oscillation
+from libpurecool.dyson_pure_cool import DysonPureCool
+from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
+from libpurecool.dyson_pure_state import DysonPureCoolState
+from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State
+
+import homeassistant.components.dyson.fan as dyson
from homeassistant.components import dyson as dyson_parent
-from homeassistant.components.dyson import DYSON_DEVICES, fan as dyson
-from homeassistant.components.fan import (ATTR_SPEED, ATTR_SPEED_LIST,
- ATTR_OSCILLATING)
+from homeassistant.components.dyson import DYSON_DEVICES
+from homeassistant.components.fan import (DOMAIN, ATTR_SPEED, ATTR_SPEED_LIST,
+ ATTR_OSCILLATING, SPEED_LOW,
+ SPEED_MEDIUM, SPEED_HIGH,
+ SERVICE_OSCILLATE)
+from homeassistant.const import (SERVICE_TURN_ON,
+ SERVICE_TURN_OFF,
+ ATTR_ENTITY_ID)
+from homeassistant.helpers import discovery
+from homeassistant.setup import setup_component, async_setup_component
from tests.common import get_test_home_assistant
-from libpurecoollink.const import FanSpeed, FanMode, NightMode, Oscillation
-from libpurecoollink.dyson_pure_state import DysonPureCoolState
-from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
class MockDysonState(DysonPureCoolState):
@@ -21,6 +33,58 @@ class MockDysonState(DysonPureCoolState):
pass
+def _get_dyson_purecool_device():
+ """Return a valid device as provided by the Dyson web services."""
+ device = mock.Mock(spec=DysonPureCool)
+ device.serial = "XX-XXXXX-XX"
+ device.name = "Living room"
+ device.connect = mock.Mock(return_value=True)
+ device.auto_connect = mock.Mock(return_value=True)
+ device.state = mock.Mock()
+ device.state.oscillation = "OION"
+ device.state.fan_power = "ON"
+ device.state.speed = FanSpeed.FAN_SPEED_AUTO.value
+ device.state.night_mode = "OFF"
+ device.state.auto_mode = "ON"
+ device.state.oscillation_angle_low = "0090"
+ device.state.oscillation_angle_high = "0180"
+ device.state.front_direction = "ON"
+ device.state.sleep_timer = 60
+ device.state.hepa_filter_state = "0090"
+ device.state.carbon_filter_state = "0080"
+ return device
+
+
+def _get_supported_speeds():
+ return [
+ int(FanSpeed.FAN_SPEED_1.value),
+ int(FanSpeed.FAN_SPEED_2.value),
+ int(FanSpeed.FAN_SPEED_3.value),
+ int(FanSpeed.FAN_SPEED_4.value),
+ int(FanSpeed.FAN_SPEED_5.value),
+ int(FanSpeed.FAN_SPEED_6.value),
+ int(FanSpeed.FAN_SPEED_7.value),
+ int(FanSpeed.FAN_SPEED_8.value),
+ int(FanSpeed.FAN_SPEED_9.value),
+ int(FanSpeed.FAN_SPEED_10.value),
+ ]
+
+
+def _get_config():
+ """Return a config dictionary."""
+ return {dyson_parent.DOMAIN: {
+ dyson_parent.CONF_USERNAME: "email",
+ dyson_parent.CONF_PASSWORD: "password",
+ dyson_parent.CONF_LANGUAGE: "GB",
+ dyson_parent.CONF_DEVICES: [
+ {
+ "device_id": "XX-XXXXX-XX",
+ "device_ip": "192.168.0.1"
+ }
+ ]
+ }}
+
+
def _get_device_with_no_state():
"""Return a device with no state."""
device = mock.Mock()
@@ -64,8 +128,8 @@ def _get_device_on():
return device
-class DysonTest(unittest.TestCase):
- """Dyson Sensor component test class."""
+class DysonSetupTest(unittest.TestCase):
+ """Dyson component setup tests."""
def setUp(self): # pylint: disable=invalid-name
"""Set up things to be run when tests are started."""
@@ -79,24 +143,39 @@ class DysonTest(unittest.TestCase):
"""Test setup component with no devices."""
self.hass.data[dyson.DYSON_DEVICES] = []
add_entities = mock.MagicMock()
- dyson.setup_platform(self.hass, None, add_entities)
+ dyson.setup_platform(self.hass, None, add_entities, mock.Mock())
add_entities.assert_called_with([])
def test_setup_component(self):
"""Test setup component with devices."""
def _add_device(devices):
- assert len(devices) == 1
+ assert len(devices) == 2
assert devices[0].name == "Device_name"
device_fan = _get_device_on()
+ device_purecool_fan = _get_dyson_purecool_device()
device_non_fan = _get_device_off()
- self.hass.data[dyson.DYSON_DEVICES] = [device_fan, device_non_fan]
+ self.hass.data[dyson.DYSON_DEVICES] = [device_fan,
+ device_purecool_fan,
+ device_non_fan]
dyson.setup_platform(self.hass, None, _add_device)
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+
+class DysonTest(unittest.TestCase):
+ """Dyson fan component test class."""
+
+ def setUp(self): # pylint: disable=invalid-name
+ """Set up things to be run when tests are started."""
+ self.hass = get_test_home_assistant()
+
+ def tearDown(self): # pylint: disable=invalid-name
+ """Stop everything that was started."""
+ self.hass.stop()
+
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_device_on()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_get_state_attributes(self, mocked_login, mocked_devices):
"""Test async added to hass."""
setup_component(self.hass, dyson_parent.DOMAIN, {
@@ -108,18 +187,18 @@ class DysonTest(unittest.TestCase):
})
self.hass.block_till_done()
state = self.hass.states.get("{}.{}".format(
- dyson.DOMAIN,
+ DOMAIN,
mocked_devices.return_value[0].name))
- assert dyson.ATTR_IS_NIGHT_MODE in state.attributes
- assert dyson.ATTR_IS_AUTO_MODE in state.attributes
+ assert dyson.ATTR_NIGHT_MODE in state.attributes
+ assert dyson.ATTR_AUTO_MODE in state.attributes
assert ATTR_SPEED in state.attributes
assert ATTR_SPEED_LIST in state.attributes
assert ATTR_OSCILLATING in state.attributes
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_device_on()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_async_added_to_hass(self, mocked_login, mocked_devices):
"""Test async added to hass."""
setup_component(self.hass, dyson_parent.DOMAIN, {
@@ -161,11 +240,11 @@ class DysonTest(unittest.TestCase):
device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device)
assert not component.should_poll
- component.night_mode(True)
+ component.set_night_mode(True)
set_config = device.set_configuration
set_config.assert_called_with(night_mode=NightMode.NIGHT_MODE_ON)
- component.night_mode(False)
+ component.set_night_mode(False)
set_config = device.set_configuration
set_config.assert_called_with(night_mode=NightMode.NIGHT_MODE_OFF)
@@ -173,22 +252,22 @@ class DysonTest(unittest.TestCase):
"""Test night mode."""
device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device)
- assert not component.is_night_mode
+ assert not component.night_mode
device = _get_device_off()
component = dyson.DysonPureCoolLinkDevice(self.hass, device)
- assert component.is_night_mode
+ assert component.night_mode
def test_dyson_turn_auto_mode(self):
"""Test turn on/off fan with auto mode."""
device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device)
assert not component.should_poll
- component.auto_mode(True)
+ component.set_auto_mode(True)
set_config = device.set_configuration
set_config.assert_called_with(fan_mode=FanMode.AUTO)
- component.auto_mode(False)
+ component.set_auto_mode(False)
set_config = device.set_configuration
set_config.assert_called_with(fan_mode=FanMode.FAN)
@@ -196,11 +275,11 @@ class DysonTest(unittest.TestCase):
"""Test auto mode."""
device = _get_device_on()
component = dyson.DysonPureCoolLinkDevice(self.hass, device)
- assert not component.is_auto_mode
+ assert not component.auto_mode
device = _get_device_auto()
component = dyson.DysonPureCoolLinkDevice(self.hass, device)
- assert component.is_auto_mode
+ assert component.auto_mode
def test_dyson_turn_on_speed(self):
"""Test turn on fan with specified speed."""
@@ -320,14 +399,355 @@ class DysonTest(unittest.TestCase):
self.hass.data[DYSON_DEVICES] = []
dyson_device.entity_id = 'fan.living_room'
self.hass.data[dyson.DYSON_FAN_DEVICES] = [dyson_device]
- dyson.setup_platform(self.hass, None, mock.MagicMock())
+ dyson.setup_platform(self.hass, None,
+ mock.MagicMock(), mock.MagicMock())
- self.hass.services.call(dyson.DOMAIN, dyson.SERVICE_SET_NIGHT_MODE,
+ self.hass.services.call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_NIGHT_MODE,
{"entity_id": "fan.bed_room",
"night_mode": True}, True)
- assert not dyson_device.night_mode.called
+ assert dyson_device.set_night_mode.call_count == 0
- self.hass.services.call(dyson.DOMAIN, dyson.SERVICE_SET_NIGHT_MODE,
+ self.hass.services.call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_NIGHT_MODE,
{"entity_id": "fan.living_room",
"night_mode": True}, True)
- dyson_device.night_mode.assert_called_with(True)
+ dyson_device.set_night_mode.assert_called_with(True)
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_turn_on(devices, login, hass):
+ """Test turn on."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: "fan.bed_room"}, True)
+ assert device.turn_on.call_count == 0
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: "fan.living_room"}, True)
+ assert device.turn_on.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_speed(devices, login, hass):
+ """Test set speed."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ ATTR_SPEED: SPEED_LOW}, True)
+ assert device.set_fan_speed.call_count == 0
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ ATTR_SPEED: SPEED_LOW}, True)
+ device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_4)
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ ATTR_SPEED: SPEED_MEDIUM}, True)
+ device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_7)
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ ATTR_SPEED: SPEED_HIGH}, True)
+ device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_10)
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_turn_off(devices, login, hass):
+ """Test turn off."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF,
+ {ATTR_ENTITY_ID: "fan.bed_room"}, True)
+ assert device.turn_off.call_count == 0
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF,
+ {ATTR_ENTITY_ID: "fan.living_room"}, True)
+ assert device.turn_off.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_dyson_speed(devices, login, hass):
+ """Test set exact dyson speed."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_DYSON_SPEED,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ dyson.ATTR_DYSON_SPEED:
+ int(FanSpeed.FAN_SPEED_2.value)},
+ True)
+ assert device.set_fan_speed.call_count == 0
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_DYSON_SPEED,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_DYSON_SPEED:
+ int(FanSpeed.FAN_SPEED_2.value)},
+ True)
+ device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_2)
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_oscillate(devices, login, hass):
+ """Test set oscillation."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(DOMAIN, SERVICE_OSCILLATE,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ ATTR_OSCILLATING: True}, True)
+ assert device.enable_oscillation.call_count == 0
+
+ await hass.services.async_call(DOMAIN, SERVICE_OSCILLATE,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ ATTR_OSCILLATING: True}, True)
+ assert device.enable_oscillation.call_count == 1
+
+ await hass.services.async_call(DOMAIN, SERVICE_OSCILLATE,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ ATTR_OSCILLATING: False}, True)
+ assert device.disable_oscillation.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_night_mode(devices, login, hass):
+ """Test set night mode."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_NIGHT_MODE,
+ {"entity_id": "fan.bed_room",
+ "night_mode": True}, True)
+ assert device.enable_night_mode.call_count == 0
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_NIGHT_MODE,
+ {"entity_id": "fan.living_room",
+ "night_mode": True}, True)
+ assert device.enable_night_mode.call_count == 1
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_NIGHT_MODE,
+ {"entity_id": "fan.living_room",
+ "night_mode": False}, True)
+ assert device.disable_night_mode.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_auto_mode(devices, login, hass):
+ """Test set auto mode."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_AUTO_MODE,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ dyson.ATTR_AUTO_MODE: True}, True)
+ assert device.enable_auto_mode.call_count == 0
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_AUTO_MODE,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_AUTO_MODE: True}, True)
+ assert device.enable_auto_mode.call_count == 1
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_AUTO_MODE,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_AUTO_MODE: False}, True)
+ assert device.disable_auto_mode.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_angle(devices, login, hass):
+ """Test set angle."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN, dyson.SERVICE_SET_ANGLE,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ dyson.ATTR_ANGLE_LOW: 90,
+ dyson.ATTR_ANGLE_HIGH: 180}, True)
+ assert device.enable_oscillation.call_count == 0
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN, dyson.SERVICE_SET_ANGLE,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_ANGLE_LOW: 90,
+ dyson.ATTR_ANGLE_HIGH: 180}, True)
+ device.enable_oscillation.assert_called_with(90, 180)
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_flow_direction_front(devices, login, hass):
+ """Test set frontal flow direction."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_FLOW_DIRECTION_FRONT,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ dyson.ATTR_FLOW_DIRECTION_FRONT: True},
+ True)
+ assert device.enable_frontal_direction.call_count == 0
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_FLOW_DIRECTION_FRONT,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_FLOW_DIRECTION_FRONT: True},
+ True)
+ assert device.enable_frontal_direction.call_count == 1
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN,
+ dyson.SERVICE_SET_FLOW_DIRECTION_FRONT,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_FLOW_DIRECTION_FRONT: False},
+ True)
+ assert device.disable_frontal_direction.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_set_timer(devices, login, hass):
+ """Test set timer."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN, dyson.SERVICE_SET_TIMER,
+ {ATTR_ENTITY_ID: "fan.bed_room",
+ dyson.ATTR_TIMER: 60},
+ True)
+ assert device.enable_frontal_direction.call_count == 0
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN, dyson.SERVICE_SET_TIMER,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_TIMER: 60},
+ True)
+ device.enable_sleep_timer.assert_called_with(60)
+
+ await hass.services.async_call(dyson.DYSON_DOMAIN, dyson.SERVICE_SET_TIMER,
+ {ATTR_ENTITY_ID: "fan.living_room",
+ dyson.ATTR_TIMER: 0},
+ True)
+ assert device.disable_sleep_timer.call_count == 1
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_attributes(devices, login, hass):
+ """Test state attributes."""
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+ fan_state = hass.states.get("fan.living_room")
+ attributes = fan_state.attributes
+
+ assert fan_state.state == "on"
+ assert attributes[dyson.ATTR_NIGHT_MODE] is False
+ assert attributes[dyson.ATTR_AUTO_MODE] is True
+ assert attributes[dyson.ATTR_ANGLE_LOW] == 90
+ assert attributes[dyson.ATTR_ANGLE_HIGH] == 180
+ assert attributes[dyson.ATTR_FLOW_DIRECTION_FRONT] is True
+ assert attributes[dyson.ATTR_TIMER] == 60
+ assert attributes[dyson.ATTR_HEPA_FILTER] == 90
+ assert attributes[dyson.ATTR_CARBON_FILTER] == 80
+ assert attributes[dyson.ATTR_DYSON_SPEED] == FanSpeed.FAN_SPEED_AUTO.value
+ assert attributes[ATTR_SPEED] == SPEED_MEDIUM
+ assert attributes[ATTR_OSCILLATING] is True
+ assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds()
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_update_state(devices, login, hass):
+ """Test state update."""
+ device = devices.return_value[0]
+ await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config())
+ await hass.async_block_till_done()
+ event = {"msg": "CURRENT-STATE",
+ "product-state": {"fpwr": "OFF", "fdir": "OFF", "auto": "OFF",
+ "oscs": "ON", "oson": "ON", "nmod": "OFF",
+ "rhtm": "ON", "fnst": "FAN", "ercd": "11E1",
+ "wacd": "NONE", "nmdv": "0004", "fnsp": "0002",
+ "bril": "0002", "corf": "ON", "cflr": "0085",
+ "hflr": "0095", "sltm": "OFF", "osal": "0045",
+ "osau": "0095", "ancp": "CUST"}}
+ device.state = DysonPureCoolV2State(json.dumps(event))
+
+ callback = device.add_message_listener.call_args_list[0][0][0]
+ callback(device.state)
+ await hass.async_block_till_done()
+ fan_state = hass.states.get("fan.living_room")
+ attributes = fan_state.attributes
+
+ assert fan_state.state == "off"
+ assert attributes[dyson.ATTR_NIGHT_MODE] is False
+ assert attributes[dyson.ATTR_AUTO_MODE] is False
+ assert attributes[dyson.ATTR_ANGLE_LOW] == 45
+ assert attributes[dyson.ATTR_ANGLE_HIGH] == 95
+ assert attributes[dyson.ATTR_FLOW_DIRECTION_FRONT] is False
+ assert attributes[dyson.ATTR_TIMER] == "OFF"
+ assert attributes[dyson.ATTR_HEPA_FILTER] == 95
+ assert attributes[dyson.ATTR_CARBON_FILTER] == 85
+ assert attributes[dyson.ATTR_DYSON_SPEED] == \
+ int(FanSpeed.FAN_SPEED_2.value)
+ assert attributes[ATTR_SPEED] is SPEED_LOW
+ assert attributes[ATTR_OSCILLATING] is False
+ assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds()
+
+
+@asynctest.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
+@asynctest.patch('libpurecool.dyson.DysonAccount.devices',
+ return_value=[_get_dyson_purecool_device()])
+async def test_purecool_component_setup_only_once(devices, login, hass):
+ """Test if entities are created only once."""
+ config = _get_config()
+ await async_setup_component(hass, dyson_parent.DOMAIN, config)
+ await hass.async_block_till_done()
+ discovery.load_platform(hass, "fan", dyson_parent.DOMAIN, {}, config)
+ await hass.async_block_till_done()
+
+ fans = [fan for fan in hass.data[DOMAIN].entities
+ if fan.platform.platform_name == dyson_parent.DOMAIN]
+
+ assert len(fans) == 1
+ assert fans[0].device_serial == "XX-XXXXX-XX"
diff --git a/tests/components/dyson/test_init.py b/tests/components/dyson/test_init.py
index 2e7b05b06cd..cc8c04a1559 100644
--- a/tests/components/dyson/test_init.py
+++ b/tests/components/dyson/test_init.py
@@ -43,7 +43,7 @@ class DysonTest(unittest.TestCase):
"""Stop everything that was started."""
self.hass.stop()
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=False)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=False)
def test_dyson_login_failed(self, mocked_login):
"""Test if Dyson connection failed."""
dyson.setup(self.hass, {dyson.DOMAIN: {
@@ -53,8 +53,8 @@ class DysonTest(unittest.TestCase):
}})
assert mocked_login.call_count == 1
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices', return_value=[])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.devices', return_value=[])
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_login(self, mocked_login, mocked_devices):
"""Test valid connection to dyson web service."""
dyson.setup(self.hass, {dyson.DOMAIN: {
@@ -67,9 +67,9 @@ class DysonTest(unittest.TestCase):
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0
@mock.patch('homeassistant.helpers.discovery.load_platform')
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_dyson_account_device_available()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_custom_conf(self, mocked_login, mocked_devices,
mocked_discovery):
"""Test device connection using custom configuration."""
@@ -89,9 +89,9 @@ class DysonTest(unittest.TestCase):
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1
assert mocked_discovery.call_count == 4
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_dyson_account_device_not_available()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_custom_conf_device_not_available(self, mocked_login,
mocked_devices):
"""Test device connection with an invalid device."""
@@ -110,9 +110,9 @@ class DysonTest(unittest.TestCase):
assert mocked_devices.call_count == 1
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_dyson_account_device_error()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_custom_conf_device_error(self, mocked_login,
mocked_devices):
"""Test device connection with device raising an exception."""
@@ -132,9 +132,9 @@ class DysonTest(unittest.TestCase):
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0
@mock.patch('homeassistant.helpers.discovery.load_platform')
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_dyson_account_device_available()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_custom_conf_with_unknown_device(self, mocked_login,
mocked_devices,
mocked_discovery):
@@ -156,9 +156,9 @@ class DysonTest(unittest.TestCase):
assert mocked_discovery.call_count == 0
@mock.patch('homeassistant.helpers.discovery.load_platform')
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_dyson_account_device_available()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_discovery(self, mocked_login, mocked_devices,
mocked_discovery):
"""Test device connection using discovery."""
@@ -174,9 +174,9 @@ class DysonTest(unittest.TestCase):
assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1
assert mocked_discovery.call_count == 4
- @mock.patch('libpurecoollink.dyson.DysonAccount.devices',
+ @mock.patch('libpurecool.dyson.DysonAccount.devices',
return_value=[_get_dyson_account_device_not_available()])
- @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
+ @mock.patch('libpurecool.dyson.DysonAccount.login', return_value=True)
def test_dyson_discovery_device_not_available(self, mocked_login,
mocked_devices):
"""Test device connection with discovery and invalid device."""
diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py
index 3218038c7e3..67c34d4d180 100644
--- a/tests/components/dyson/test_sensor.py
+++ b/tests/components/dyson/test_sensor.py
@@ -2,16 +2,17 @@
import unittest
from unittest import mock
+from libpurecool.dyson_pure_cool_link import DysonPureCoolLink
+
+from homeassistant.components.dyson import sensor as dyson
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, \
STATE_OFF
-from homeassistant.components.dyson import sensor as dyson
from tests.common import get_test_home_assistant
-from libpurecoollink.dyson_pure_cool_link import DysonPureCoolLink
def _get_device_without_state():
"""Return a valid device provide by Dyson web services."""
- device = mock.Mock(spec=DysonPureCoolLink)
+ device = mock.Mock()
device.name = "Device_name"
device.state = None
device.environmental_state = None
@@ -20,7 +21,7 @@ def _get_device_without_state():
def _get_with_state():
"""Return a valid device with state values."""
- device = mock.Mock()
+ device = mock.Mock(spec=DysonPureCoolLink)
device.name = "Device_name"
device.state = mock.Mock()
device.state.filter_life = 100
diff --git a/tests/components/dyson/test_vacuum.py b/tests/components/dyson/test_vacuum.py
index 05ad8cf0db7..cdf76c975ae 100644
--- a/tests/components/dyson/test_vacuum.py
+++ b/tests/components/dyson/test_vacuum.py
@@ -2,8 +2,8 @@
import unittest
from unittest import mock
-from libpurecoollink.dyson_360_eye import Dyson360Eye
-from libpurecoollink.const import Dyson360EyeMode, PowerMode
+from libpurecool.const import Dyson360EyeMode, PowerMode
+from libpurecool.dyson_360_eye import Dyson360Eye
from homeassistant.components.dyson import vacuum as dyson
from homeassistant.components.dyson.vacuum import Dyson360EyeDevice
diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py
index 08001b0ebab..3348fdfe87b 100644
--- a/tests/components/emulated_hue/test_hue_api.py
+++ b/tests/components/emulated_hue/test_hue_api.py
@@ -13,7 +13,8 @@ from homeassistant.components import (
fan, http, light, script, emulated_hue, media_player, cover, climate)
from homeassistant.components.emulated_hue import Config
from homeassistant.components.emulated_hue.hue_api import (
- HUE_API_STATE_ON, HUE_API_STATE_BRI, HueUsernameView, HueOneLightStateView,
+ HUE_API_STATE_ON, HUE_API_STATE_BRI, HUE_API_STATE_HUE, HUE_API_STATE_SAT,
+ HueUsernameView, HueOneLightStateView,
HueAllLightsStateView, HueOneLightChangeView, HueAllGroupsStateView)
from homeassistant.const import STATE_ON, STATE_OFF
@@ -221,12 +222,13 @@ def test_discover_lights(hue_client):
@asyncio.coroutine
def test_get_light_state(hass_hue, hue_client):
"""Test the getting of light state."""
- # Turn office light on and set to 127 brightness
+ # Turn office light on and set to 127 brightness, and set light color
yield from hass_hue.services.async_call(
light.DOMAIN, const.SERVICE_TURN_ON,
{
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
- light.ATTR_BRIGHTNESS: 127
+ light.ATTR_BRIGHTNESS: 127,
+ light.ATTR_RGB_COLOR: (1, 2, 7)
},
blocking=True)
@@ -235,6 +237,8 @@ def test_get_light_state(hass_hue, hue_client):
assert office_json['state'][HUE_API_STATE_ON] is True
assert office_json['state'][HUE_API_STATE_BRI] == 127
+ assert office_json['state'][HUE_API_STATE_HUE] == 41869
+ assert office_json['state'][HUE_API_STATE_SAT] == 217
# Check all lights view
result = yield from hue_client.get('/api/username/lights')
@@ -261,6 +265,8 @@ def test_get_light_state(hass_hue, hue_client):
assert office_json['state'][HUE_API_STATE_ON] is False
assert office_json['state'][HUE_API_STATE_BRI] == 0
+ assert office_json['state'][HUE_API_STATE_HUE] == 0
+ assert office_json['state'][HUE_API_STATE_SAT] == 0
# Make sure bedroom light isn't accessible
yield from perform_get_light_state(
@@ -287,6 +293,19 @@ def test_put_light_state(hass_hue, hue_client):
assert ceiling_lights.state == STATE_ON
assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 153
+ # update light state through api
+ yield from perform_put_light_state(
+ hass_hue, hue_client,
+ 'light.ceiling_lights', True,
+ hue=4369, saturation=127, brightness=123)
+
+ # go through api to get the state back
+ ceiling_json = yield from perform_get_light_state(
+ hue_client, 'light.ceiling_lights', 200)
+ assert ceiling_json['state'][HUE_API_STATE_BRI] == 123
+ assert ceiling_json['state'][HUE_API_STATE_HUE] == 4369
+ assert ceiling_json['state'][HUE_API_STATE_SAT] == 127
+
# Go through the API to turn it off
ceiling_result = yield from perform_put_light_state(
hass_hue, hue_client,
@@ -302,6 +321,11 @@ def test_put_light_state(hass_hue, hue_client):
# Check to make sure the state changed
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
assert ceiling_lights.state == STATE_OFF
+ ceiling_json = yield from perform_get_light_state(
+ hue_client, 'light.ceiling_lights', 200)
+ assert ceiling_json['state'][HUE_API_STATE_BRI] == 0
+ assert ceiling_json['state'][HUE_API_STATE_HUE] == 0
+ assert ceiling_json['state'][HUE_API_STATE_SAT] == 0
# Make sure we can't change the bedroom light state
bedroom_result = yield from perform_put_light_state(
@@ -706,7 +730,8 @@ def perform_get_light_state(client, entity_id, expected_status):
@asyncio.coroutine
def perform_put_light_state(hass_hue, client, entity_id, is_on,
- brightness=None, content_type='application/json'):
+ brightness=None, content_type='application/json',
+ hue=None, saturation=None):
"""Test the setting of a light state."""
req_headers = {'Content-Type': content_type}
@@ -714,6 +739,10 @@ def perform_put_light_state(hass_hue, client, entity_id, is_on,
if brightness is not None:
data[HUE_API_STATE_BRI] = brightness
+ if hue is not None:
+ data[HUE_API_STATE_HUE] = hue
+ if saturation is not None:
+ data[HUE_API_STATE_SAT] = saturation
result = yield from client.put(
'/api/username/lights/{}/state'.format(entity_id), headers=req_headers,
diff --git a/tests/components/ffmpeg/test_sensor.py b/tests/components/ffmpeg/test_sensor.py
index d1fd6124b4c..19c497514b7 100644
--- a/tests/components/ffmpeg/test_sensor.py
+++ b/tests/components/ffmpeg/test_sensor.py
@@ -29,6 +29,7 @@ class TestFFmpegNoiseSetup:
"""Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
+ self.hass.block_till_done()
assert self.hass.data['ffmpeg'].binary == 'ffmpeg'
assert self.hass.states.get('binary_sensor.ffmpeg_noise') is not None
@@ -39,6 +40,7 @@ class TestFFmpegNoiseSetup:
"""Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
+ self.hass.block_till_done()
assert self.hass.data['ffmpeg'].binary == 'ffmpeg'
assert self.hass.states.get('binary_sensor.ffmpeg_noise') is not None
@@ -54,6 +56,7 @@ class TestFFmpegNoiseSetup:
"""Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
+ self.hass.block_till_done()
assert self.hass.data['ffmpeg'].binary == 'ffmpeg'
assert self.hass.states.get('binary_sensor.ffmpeg_noise') is not None
@@ -92,6 +95,7 @@ class TestFFmpegMotionSetup:
"""Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
+ self.hass.block_till_done()
assert self.hass.data['ffmpeg'].binary == 'ffmpeg'
assert self.hass.states.get('binary_sensor.ffmpeg_motion') is not None
@@ -102,6 +106,7 @@ class TestFFmpegMotionSetup:
"""Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
+ self.hass.block_till_done()
assert self.hass.data['ffmpeg'].binary == 'ffmpeg'
assert self.hass.states.get('binary_sensor.ffmpeg_motion') is not None
@@ -117,6 +122,7 @@ class TestFFmpegMotionSetup:
"""Set up ffmpeg component."""
with assert_setup_component(1, 'binary_sensor'):
setup_component(self.hass, 'binary_sensor', self.config)
+ self.hass.block_till_done()
assert self.hass.data['ffmpeg'].binary == 'ffmpeg'
assert self.hass.states.get('binary_sensor.ffmpeg_motion') is not None
diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py
index c43f1071e33..ee4e2e4e77c 100644
--- a/tests/components/flux/test_switch.py
+++ b/tests/components/flux/test_switch.py
@@ -6,7 +6,6 @@ from homeassistant.setup import setup_component
from homeassistant.components import switch, light
from homeassistant.const import (
CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SUN_EVENT_SUNRISE)
-import homeassistant.loader as loader
import homeassistant.util.dt as dt_util
from tests.common import (
@@ -74,7 +73,7 @@ class TestSwitchFlux(unittest.TestCase):
def test_flux_when_switch_is_off(self):
"""Test the flux switch when it is off."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -114,7 +113,7 @@ class TestSwitchFlux(unittest.TestCase):
def test_flux_before_sunrise(self):
"""Test the flux switch before sunrise."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -159,7 +158,7 @@ class TestSwitchFlux(unittest.TestCase):
# pylint: disable=invalid-name
def test_flux_after_sunrise_before_sunset(self):
"""Test the flux switch after sunrise and before sunset."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -205,7 +204,7 @@ class TestSwitchFlux(unittest.TestCase):
# pylint: disable=invalid-name
def test_flux_after_sunset_before_stop(self):
"""Test the flux switch after sunset and before stop."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -252,7 +251,7 @@ class TestSwitchFlux(unittest.TestCase):
# pylint: disable=invalid-name
def test_flux_after_stop_before_sunrise(self):
"""Test the flux switch after stop and before sunrise."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -297,7 +296,7 @@ class TestSwitchFlux(unittest.TestCase):
# pylint: disable=invalid-name
def test_flux_with_custom_start_stop_times(self):
"""Test the flux with custom start and stop times."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -347,7 +346,7 @@ class TestSwitchFlux(unittest.TestCase):
This test has the stop_time on the next day (after midnight).
"""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -398,7 +397,7 @@ class TestSwitchFlux(unittest.TestCase):
This test has the stop_time on the next day (after midnight).
"""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -448,7 +447,7 @@ class TestSwitchFlux(unittest.TestCase):
This test has the stop_time on the next day (after midnight).
"""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -497,7 +496,7 @@ class TestSwitchFlux(unittest.TestCase):
This test has the stop_time on the next day (after midnight).
"""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -547,7 +546,7 @@ class TestSwitchFlux(unittest.TestCase):
This test has the stop_time on the next day (after midnight).
"""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -594,7 +593,7 @@ class TestSwitchFlux(unittest.TestCase):
# pylint: disable=invalid-name
def test_flux_with_custom_colortemps(self):
"""Test the flux with custom start and stop colortemps."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -643,7 +642,7 @@ class TestSwitchFlux(unittest.TestCase):
# pylint: disable=invalid-name
def test_flux_with_custom_brightness(self):
"""Test the flux with custom start and stop colortemps."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -690,7 +689,7 @@ class TestSwitchFlux(unittest.TestCase):
def test_flux_with_multiple_lights(self):
"""Test the flux switch with multiple light entities."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -758,7 +757,7 @@ class TestSwitchFlux(unittest.TestCase):
def test_flux_with_mired(self):
"""Test the flux switch´s mode mired."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
@@ -802,7 +801,7 @@ class TestSwitchFlux(unittest.TestCase):
def test_flux_with_rgb(self):
"""Test the flux switch´s mode rgb."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
{light.DOMAIN: {CONF_PLATFORM: 'test'}})
diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py
index e4ed2c15ecb..1fc72b4149b 100644
--- a/tests/components/frontend/test_init.py
+++ b/tests/components/frontend/test_init.py
@@ -78,9 +78,9 @@ def test_frontend_and_static(mock_http_client, mock_onboarded):
# Test we can retrieve frontend.js
frontendjs = re.search(
- r'(?P\/frontend_es5\/app-[A-Za-z0-9]{8}.js)', text)
+ r'(?P\/frontend_es5\/app.[A-Za-z0-9]{8}.js)', text)
- assert frontendjs is not None
+ assert frontendjs is not None, text
resp = yield from mock_http_client.get(frontendjs.groups(0)[0])
assert resp.status == 200
assert 'public' in resp.headers.get('cache-control')
@@ -210,7 +210,7 @@ async def test_themes_reload_themes(hass, hass_ws_client):
async def test_missing_themes(hass, hass_ws_client):
"""Test that themes API works when themes are not defined."""
- await async_setup_component(hass, 'frontend')
+ await async_setup_component(hass, 'frontend', {})
client = await hass_ws_client(hass)
await client.send_json({
@@ -247,7 +247,7 @@ def test_extra_urls_es5(mock_http_client_with_urls, mock_onboarded):
async def test_get_panels(hass, hass_ws_client):
"""Test get_panels command."""
- await async_setup_component(hass, 'frontend')
+ await async_setup_component(hass, 'frontend', {})
await hass.components.frontend.async_register_built_in_panel(
'map', 'Map', 'mdi:tooltip-account', require_admin=True)
@@ -272,7 +272,7 @@ async def test_get_panels(hass, hass_ws_client):
async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user):
"""Test get_panels command."""
hass_admin_user.groups = []
- await async_setup_component(hass, 'frontend')
+ await async_setup_component(hass, 'frontend', {})
await hass.components.frontend.async_register_built_in_panel(
'map', 'Map', 'mdi:tooltip-account', require_admin=True)
await hass.components.frontend.async_register_built_in_panel(
@@ -295,7 +295,7 @@ async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user):
async def test_get_translations(hass, hass_ws_client):
"""Test get_translations command."""
- await async_setup_component(hass, 'frontend')
+ await async_setup_component(hass, 'frontend', {})
client = await hass_ws_client(hass)
with patch('homeassistant.components.frontend.async_get_translations',
diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py
index 49d49fdd3d4..60d2250a13d 100644
--- a/tests/components/generic_thermostat/test_climate.py
+++ b/tests/components/generic_thermostat/test_climate.py
@@ -18,7 +18,6 @@ from homeassistant.const import (
TEMP_FAHRENHEIT,
ATTR_TEMPERATURE
)
-from homeassistant import loader
from homeassistant.util.unit_system import METRIC_SYSTEM
from homeassistant.components import input_boolean, switch
from homeassistant.components.climate.const import (
@@ -98,7 +97,7 @@ async def test_heater_input_boolean(hass, setup_comp_1):
async def test_heater_switch(hass, setup_comp_1):
"""Test heater switching test switch."""
- platform = loader.get_component(hass, 'switch.test')
+ platform = getattr(hass.components, 'test.switch')
platform.init()
switch_1 = platform.DEVICES[1]
assert await async_setup_component(hass, switch.DOMAIN, {'switch': {
@@ -112,6 +111,7 @@ async def test_heater_switch(hass, setup_comp_1):
'target_sensor': ENT_SENSOR
}})
+ await hass.async_block_till_done()
assert STATE_OFF == \
hass.states.get(heater_switch).state
diff --git a/tests/components/geo_location/test_init.py b/tests/components/geo_location/test_init.py
index a3b04848b6d..00cb2a872d2 100644
--- a/tests/components/geo_location/test_init.py
+++ b/tests/components/geo_location/test_init.py
@@ -8,7 +8,7 @@ from homeassistant.setup import async_setup_component
async def test_setup_component(hass):
"""Simple test setup of component."""
- result = await async_setup_component(hass, geo_location.DOMAIN)
+ result = await async_setup_component(hass, geo_location.DOMAIN, {})
assert result
diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py
index dd87a6d9503..98edd8b3af1 100644
--- a/tests/components/geofency/test_init.py
+++ b/tests/components/geofency/test_init.py
@@ -113,34 +113,34 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture
-def geofency_client(loop, hass, aiohttp_client):
+async def geofency_client(loop, hass, aiohttp_client):
"""Geofency mock client (unauthenticated)."""
- assert loop.run_until_complete(async_setup_component(
- hass, 'persistent_notification', {}))
+ assert await async_setup_component(
+ hass, 'persistent_notification', {})
- assert loop.run_until_complete(async_setup_component(
+ assert await async_setup_component(
hass, DOMAIN, {
DOMAIN: {
CONF_MOBILE_BEACONS: ['Car 1']
- }}))
-
- loop.run_until_complete(hass.async_block_till_done())
+ }})
+ await hass.async_block_till_done()
with patch('homeassistant.components.device_tracker.update_config'):
- yield loop.run_until_complete(aiohttp_client(hass.http.app))
+ return await aiohttp_client(hass.http.app)
@pytest.fixture(autouse=True)
-def setup_zones(loop, hass):
+async def setup_zones(loop, hass):
"""Set up Zone config in HA."""
- assert loop.run_until_complete(async_setup_component(
+ assert await async_setup_component(
hass, zone.DOMAIN, {
'zone': {
'name': 'Home',
'latitude': HOME_LATITUDE,
'longitude': HOME_LONGITUDE,
'radius': 100,
- }}))
+ }})
+ await hass.async_block_till_done()
@pytest.fixture
@@ -156,6 +156,7 @@ async def webhook_id(hass, geofency_client):
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ await hass.async_block_till_done()
return result['result'].data['webhook_id']
diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py
index 331c6d2d9f5..d75b51df65b 100644
--- a/tests/components/google_assistant/__init__.py
+++ b/tests/components/google_assistant/__init__.py
@@ -10,8 +10,7 @@ DEMO_DEVICES = [{
},
'traits': [
'action.devices.traits.OnOff', 'action.devices.traits.Brightness',
- 'action.devices.traits.ColorSpectrum',
- 'action.devices.traits.ColorTemperature'
+ 'action.devices.traits.ColorSetting',
],
'type':
'action.devices.types.LIGHT',
@@ -50,8 +49,7 @@ DEMO_DEVICES = [{
},
'traits': [
'action.devices.traits.OnOff', 'action.devices.traits.Brightness',
- 'action.devices.traits.ColorSpectrum',
- 'action.devices.traits.ColorTemperature'
+ 'action.devices.traits.ColorSetting',
],
'type':
'action.devices.types.LIGHT',
@@ -65,8 +63,7 @@ DEMO_DEVICES = [{
},
'traits': [
'action.devices.traits.OnOff', 'action.devices.traits.Brightness',
- 'action.devices.traits.ColorSpectrum',
- 'action.devices.traits.ColorTemperature'
+ 'action.devices.traits.ColorSetting',
],
'type':
'action.devices.types.LIGHT',
@@ -119,7 +116,7 @@ DEMO_DEVICES = [{
},
'traits': ['action.devices.traits.OpenClose'],
'type':
- 'action.devices.types.BLINDS',
+ 'action.devices.types.GARAGE',
'willReportState': False
}, {
'id': 'cover.kitchen_window',
diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py
index 60df4a8e79c..19e1858d4f5 100644
--- a/tests/components/google_assistant/test_google_assistant.py
+++ b/tests/components/google_assistant/test_google_assistant.py
@@ -174,8 +174,12 @@ def test_query_request(hass_fixture, assistant_client, auth_header):
assert devices['light.bed_light']['on'] is False
assert devices['light.ceiling_lights']['on'] is True
assert devices['light.ceiling_lights']['brightness'] == 70
- assert devices['light.kitchen_lights']['color']['spectrumRGB'] == 16727919
- assert devices['light.kitchen_lights']['color']['temperature'] == 4166
+ assert devices['light.kitchen_lights']['color']['spectrumHsv'] == {
+ 'hue': 345,
+ 'saturation': 0.75,
+ 'value': 0.7058823529411765,
+ }
+ assert devices['light.kitchen_lights']['color']['temperatureK'] == 4166
assert devices['media_player.lounge_room']['on'] is True
diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py
index bc59cc8ff2d..375f647da22 100644
--- a/tests/components/google_assistant/test_smart_home.py
+++ b/tests/components/google_assistant/test_smart_home.py
@@ -13,7 +13,10 @@ from homeassistant.components.climate.const import (
from homeassistant.components.google_assistant import (
const, trait, helpers, smart_home as sh,
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
+from homeassistant.components.demo.binary_sensor import DemoBinarySensor
+from homeassistant.components.demo.cover import DemoCover
from homeassistant.components.demo.light import DemoLight
+from homeassistant.components.demo.switch import DemoSwitch
from homeassistant.helpers import device_registry
from tests.common import (mock_device_registry, mock_registry,
@@ -21,7 +24,6 @@ from tests.common import (mock_device_registry, mock_registry,
BASIC_CONFIG = helpers.Config(
should_expose=lambda state: True,
- allow_unlock=False
)
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
@@ -56,7 +58,6 @@ async def test_sync_message(hass):
config = helpers.Config(
should_expose=lambda state: state.entity_id != 'light.not_expose',
- allow_unlock=False,
entity_config={
'light.demo_light': {
const.CONF_ROOM_HINT: 'Living Room',
@@ -93,15 +94,16 @@ async def test_sync_message(hass):
'traits': [
trait.TRAIT_BRIGHTNESS,
trait.TRAIT_ONOFF,
- trait.TRAIT_COLOR_SPECTRUM,
- trait.TRAIT_COLOR_TEMP,
+ trait.TRAIT_COLOR_SETTING,
],
- 'type': sh.TYPE_LIGHT,
+ 'type': const.TYPE_LIGHT,
'willReportState': False,
'attributes': {
- 'colorModel': 'rgb',
- 'temperatureMinK': 2000,
- 'temperatureMaxK': 6535,
+ 'colorModel': 'hsv',
+ 'colorTemperatureRange': {
+ 'temperatureMinK': 2000,
+ 'temperatureMaxK': 6535,
+ }
},
'roomHint': 'Living Room'
}]
@@ -144,7 +146,6 @@ async def test_sync_in_area(hass, registries):
config = helpers.Config(
should_expose=lambda _: True,
- allow_unlock=False,
entity_config={}
)
@@ -172,15 +173,16 @@ async def test_sync_in_area(hass, registries):
'traits': [
trait.TRAIT_BRIGHTNESS,
trait.TRAIT_ONOFF,
- trait.TRAIT_COLOR_SPECTRUM,
- trait.TRAIT_COLOR_TEMP,
+ trait.TRAIT_COLOR_SETTING,
],
- 'type': sh.TYPE_LIGHT,
+ 'type': const.TYPE_LIGHT,
'willReportState': False,
'attributes': {
- 'colorModel': 'rgb',
- 'temperatureMinK': 2000,
- 'temperatureMaxK': 6535,
+ 'colorModel': 'hsv',
+ 'colorTemperatureRange': {
+ 'temperatureMinK': 2000,
+ 'temperatureMaxK': 6535,
+ }
},
'roomHint': 'Living Room'
}]
@@ -254,8 +256,12 @@ async def test_query_message(hass):
'online': True,
'brightness': 30,
'color': {
- 'spectrumRGB': 4194303,
- 'temperature': 2500,
+ 'spectrumHsv': {
+ 'hue': 180,
+ 'saturation': 0.75,
+ 'value': 0.3058823529411765,
+ },
+ 'temperatureK': 2500,
}
},
}
@@ -340,8 +346,12 @@ async def test_execute(hass):
"online": True,
'brightness': 20,
'color': {
- 'spectrumRGB': 16773155,
- 'temperature': 2631,
+ 'spectrumHsv': {
+ 'hue': 56,
+ 'saturation': 0.86,
+ 'value': 0.2,
+ },
+ 'temperatureK': 2631,
},
}
}]
@@ -478,7 +488,7 @@ async def test_serialize_input_boolean(hass):
"""Test serializing an input boolean entity."""
state = State('input_boolean.bla', 'on')
# pylint: disable=protected-access
- entity = sh._GoogleEntity(hass, BASIC_CONFIG, state)
+ entity = sh.GoogleEntity(hass, BASIC_CONFIG, state)
result = await entity.sync_serialize()
assert result == {
'id': 'input_boolean.bla',
@@ -547,6 +557,132 @@ async def test_empty_name_doesnt_sync(hass):
}
+@pytest.mark.parametrize("device_class,google_type", [
+ ('non_existing_class', 'action.devices.types.SWITCH'),
+ ('switch', 'action.devices.types.SWITCH'),
+ ('outlet', 'action.devices.types.OUTLET')
+])
+async def test_device_class_switch(hass, device_class, google_type):
+ """Test that a cover entity syncs to the correct device type."""
+ sensor = DemoSwitch(
+ 'Demo Sensor',
+ state=False,
+ icon='mdi:switch',
+ assumed=False,
+ device_class=device_class
+ )
+ sensor.hass = hass
+ sensor.entity_id = 'switch.demo_sensor'
+ await sensor.async_update_ha_state()
+
+ result = await sh.async_handle_message(
+ hass, BASIC_CONFIG, 'test-agent',
+ {
+ "requestId": REQ_ID,
+ "inputs": [{
+ "intent": "action.devices.SYNC"
+ }]
+ })
+
+ assert result == {
+ 'requestId': REQ_ID,
+ 'payload': {
+ 'agentUserId': 'test-agent',
+ 'devices': [{
+ 'attributes': {},
+ 'id': 'switch.demo_sensor',
+ 'name': {'name': 'Demo Sensor'},
+ 'traits': ['action.devices.traits.OnOff'],
+ 'type': google_type,
+ 'willReportState': False
+ }]
+ }
+ }
+
+
+@pytest.mark.parametrize("device_class,google_type", [
+ ('door', 'action.devices.types.DOOR'),
+ ('garage_door', 'action.devices.types.SENSOR'),
+ ('lock', 'action.devices.types.SENSOR'),
+ ('opening', 'action.devices.types.SENSOR'),
+ ('window', 'action.devices.types.SENSOR'),
+])
+async def test_device_class_binary_sensor(hass, device_class, google_type):
+ """Test that a binary entity syncs to the correct device type."""
+ sensor = DemoBinarySensor(
+ 'Demo Sensor',
+ state=False,
+ device_class=device_class
+ )
+ sensor.hass = hass
+ sensor.entity_id = 'binary_sensor.demo_sensor'
+ await sensor.async_update_ha_state()
+
+ result = await sh.async_handle_message(
+ hass, BASIC_CONFIG, 'test-agent',
+ {
+ "requestId": REQ_ID,
+ "inputs": [{
+ "intent": "action.devices.SYNC"
+ }]
+ })
+
+ assert result == {
+ 'requestId': REQ_ID,
+ 'payload': {
+ 'agentUserId': 'test-agent',
+ 'devices': [{
+ 'attributes': {'queryOnlyOpenClose': True},
+ 'id': 'binary_sensor.demo_sensor',
+ 'name': {'name': 'Demo Sensor'},
+ 'traits': ['action.devices.traits.OpenClose'],
+ 'type': google_type,
+ 'willReportState': False
+ }]
+ }
+ }
+
+
+@pytest.mark.parametrize("device_class,google_type", [
+ ('non_existing_class', 'action.devices.types.BLINDS'),
+ ('door', 'action.devices.types.DOOR'),
+])
+async def test_device_class_cover(hass, device_class, google_type):
+ """Test that a binary entity syncs to the correct device type."""
+ sensor = DemoCover(
+ hass,
+ 'Demo Sensor',
+ device_class=device_class
+ )
+ sensor.hass = hass
+ sensor.entity_id = 'cover.demo_sensor'
+ await sensor.async_update_ha_state()
+
+ result = await sh.async_handle_message(
+ hass, BASIC_CONFIG, 'test-agent',
+ {
+ "requestId": REQ_ID,
+ "inputs": [{
+ "intent": "action.devices.SYNC"
+ }]
+ })
+
+ assert result == {
+ 'requestId': REQ_ID,
+ 'payload': {
+ 'agentUserId': 'test-agent',
+ 'devices': [{
+ 'attributes': {},
+ 'id': 'cover.demo_sensor',
+ 'name': {'name': 'Demo Sensor'},
+ 'traits': ['action.devices.traits.OpenClose'],
+ 'type': google_type,
+ 'willReportState': False
+ }]
+ }
+ }
+
+
async def test_query_disconnect(hass):
"""Test a disconnect message."""
result = await sh.async_handle_message(
diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py
index 81a7fbe1bf7..8b7f0788f34 100644
--- a/tests/components/google_assistant/test_trait.py
+++ b/tests/components/google_assistant/test_trait.py
@@ -4,6 +4,7 @@ from unittest.mock import patch, Mock
import pytest
from homeassistant.components import (
+ binary_sensor,
camera,
cover,
fan,
@@ -18,18 +19,18 @@ from homeassistant.components import (
group,
)
from homeassistant.components.climate import const as climate
-from homeassistant.components.google_assistant import trait, helpers, const
+from homeassistant.components.google_assistant import (
+ trait, helpers, const, error)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
- ATTR_ASSUMED_STATE)
+ ATTR_DEVICE_CLASS, ATTR_ASSUMED_STATE, STATE_UNKNOWN)
from homeassistant.core import State, DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE
from homeassistant.util import color
from tests.common import async_mock_service, mock_coro
BASIC_CONFIG = helpers.Config(
should_expose=lambda state: True,
- allow_unlock=False
)
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
@@ -40,16 +41,23 @@ BASIC_DATA = helpers.RequestData(
REQ_ID,
)
-UNSAFE_CONFIG = helpers.Config(
+PIN_CONFIG = helpers.Config(
should_expose=lambda state: True,
- allow_unlock=True,
+ secure_devices_pin='1234'
+)
+
+PIN_DATA = helpers.RequestData(
+ PIN_CONFIG,
+ 'test-agent',
+ REQ_ID,
)
async def test_brightness_light(hass):
"""Test brightness trait support for light domain."""
+ assert helpers.get_google_type(light.DOMAIN, None) is not None
assert trait.BrightnessTrait.supported(light.DOMAIN,
- light.SUPPORT_BRIGHTNESS)
+ light.SUPPORT_BRIGHTNESS, None)
trt = trait.BrightnessTrait(hass, State('light.bla', light.STATE_ON, {
light.ATTR_BRIGHTNESS: 243
@@ -67,7 +75,7 @@ async def test_brightness_light(hass):
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
await trt.execute(
trait.COMMAND_BRIGHTNESS_ABSOLUTE, BASIC_DATA,
- {'brightness': 50})
+ {'brightness': 50}, {})
await hass.async_block_till_done()
assert len(calls) == 1
@@ -86,8 +94,10 @@ async def test_brightness_light(hass):
async def test_brightness_media_player(hass):
"""Test brightness trait support for media player domain."""
+ assert helpers.get_google_type(media_player.DOMAIN, None) is not None
assert trait.BrightnessTrait.supported(media_player.DOMAIN,
- media_player.SUPPORT_VOLUME_SET)
+ media_player.SUPPORT_VOLUME_SET,
+ None)
trt = trait.BrightnessTrait(hass, State(
'media_player.bla', media_player.STATE_PLAYING, {
@@ -104,7 +114,7 @@ async def test_brightness_media_player(hass):
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET)
await trt.execute(
trait.COMMAND_BRIGHTNESS_ABSOLUTE, BASIC_DATA,
- {'brightness': 60})
+ {'brightness': 60}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'media_player.bla',
@@ -115,8 +125,9 @@ async def test_brightness_media_player(hass):
async def test_camera_stream(hass):
"""Test camera stream trait support for camera domain."""
hass.config.api = Mock(base_url='http://1.1.1.1:8123')
+ assert helpers.get_google_type(camera.DOMAIN, None) is not None
assert trait.CameraStreamTrait.supported(camera.DOMAIN,
- camera.SUPPORT_STREAM)
+ camera.SUPPORT_STREAM, None)
trt = trait.CameraStreamTrait(
hass, State('camera.bla', camera.STATE_IDLE, {}), BASIC_CONFIG
@@ -134,7 +145,7 @@ async def test_camera_stream(hass):
with patch('homeassistant.components.camera.async_request_stream',
return_value=mock_coro('/api/streams/bla')):
- await trt.execute(trait.COMMAND_GET_CAMERA_STREAM, BASIC_DATA, {})
+ await trt.execute(trait.COMMAND_GET_CAMERA_STREAM, BASIC_DATA, {}, {})
assert trt.query_attributes() == {
'cameraStreamAccessUrl': 'http://1.1.1.1:8123/api/streams/bla'
@@ -143,7 +154,8 @@ async def test_camera_stream(hass):
async def test_onoff_group(hass):
"""Test OnOff trait support for group domain."""
- assert trait.OnOffTrait.supported(group.DOMAIN, 0)
+ assert helpers.get_google_type(group.DOMAIN, None) is not None
+ assert trait.OnOffTrait.supported(group.DOMAIN, 0, None)
trt_on = trait.OnOffTrait(hass, State('group.bla', STATE_ON), BASIC_CONFIG)
@@ -163,7 +175,7 @@ async def test_onoff_group(hass):
on_calls = async_mock_service(hass, HA_DOMAIN, SERVICE_TURN_ON)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': True})
+ {'on': True}, {})
assert len(on_calls) == 1
assert on_calls[0].data == {
ATTR_ENTITY_ID: 'group.bla',
@@ -172,7 +184,7 @@ async def test_onoff_group(hass):
off_calls = async_mock_service(hass, HA_DOMAIN, SERVICE_TURN_OFF)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': False})
+ {'on': False}, {})
assert len(off_calls) == 1
assert off_calls[0].data == {
ATTR_ENTITY_ID: 'group.bla',
@@ -181,7 +193,8 @@ async def test_onoff_group(hass):
async def test_onoff_input_boolean(hass):
"""Test OnOff trait support for input_boolean domain."""
- assert trait.OnOffTrait.supported(input_boolean.DOMAIN, 0)
+ assert helpers.get_google_type(input_boolean.DOMAIN, None) is not None
+ assert trait.OnOffTrait.supported(input_boolean.DOMAIN, 0, None)
trt_on = trait.OnOffTrait(hass, State('input_boolean.bla', STATE_ON),
BASIC_CONFIG)
@@ -202,7 +215,7 @@ async def test_onoff_input_boolean(hass):
on_calls = async_mock_service(hass, input_boolean.DOMAIN, SERVICE_TURN_ON)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': True})
+ {'on': True}, {})
assert len(on_calls) == 1
assert on_calls[0].data == {
ATTR_ENTITY_ID: 'input_boolean.bla',
@@ -212,7 +225,7 @@ async def test_onoff_input_boolean(hass):
SERVICE_TURN_OFF)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': False})
+ {'on': False}, {})
assert len(off_calls) == 1
assert off_calls[0].data == {
ATTR_ENTITY_ID: 'input_boolean.bla',
@@ -221,7 +234,8 @@ async def test_onoff_input_boolean(hass):
async def test_onoff_switch(hass):
"""Test OnOff trait support for switch domain."""
- assert trait.OnOffTrait.supported(switch.DOMAIN, 0)
+ assert helpers.get_google_type(switch.DOMAIN, None) is not None
+ assert trait.OnOffTrait.supported(switch.DOMAIN, 0, None)
trt_on = trait.OnOffTrait(hass, State('switch.bla', STATE_ON),
BASIC_CONFIG)
@@ -242,7 +256,7 @@ async def test_onoff_switch(hass):
on_calls = async_mock_service(hass, switch.DOMAIN, SERVICE_TURN_ON)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': True})
+ {'on': True}, {})
assert len(on_calls) == 1
assert on_calls[0].data == {
ATTR_ENTITY_ID: 'switch.bla',
@@ -251,7 +265,7 @@ async def test_onoff_switch(hass):
off_calls = async_mock_service(hass, switch.DOMAIN, SERVICE_TURN_OFF)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': False})
+ {'on': False}, {})
assert len(off_calls) == 1
assert off_calls[0].data == {
ATTR_ENTITY_ID: 'switch.bla',
@@ -260,7 +274,8 @@ async def test_onoff_switch(hass):
async def test_onoff_fan(hass):
"""Test OnOff trait support for fan domain."""
- assert trait.OnOffTrait.supported(fan.DOMAIN, 0)
+ assert helpers.get_google_type(fan.DOMAIN, None) is not None
+ assert trait.OnOffTrait.supported(fan.DOMAIN, 0, None)
trt_on = trait.OnOffTrait(hass, State('fan.bla', STATE_ON), BASIC_CONFIG)
@@ -278,7 +293,7 @@ async def test_onoff_fan(hass):
on_calls = async_mock_service(hass, fan.DOMAIN, SERVICE_TURN_ON)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': True})
+ {'on': True}, {})
assert len(on_calls) == 1
assert on_calls[0].data == {
ATTR_ENTITY_ID: 'fan.bla',
@@ -287,7 +302,7 @@ async def test_onoff_fan(hass):
off_calls = async_mock_service(hass, fan.DOMAIN, SERVICE_TURN_OFF)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': False})
+ {'on': False}, {})
assert len(off_calls) == 1
assert off_calls[0].data == {
ATTR_ENTITY_ID: 'fan.bla',
@@ -296,7 +311,8 @@ async def test_onoff_fan(hass):
async def test_onoff_light(hass):
"""Test OnOff trait support for light domain."""
- assert trait.OnOffTrait.supported(light.DOMAIN, 0)
+ assert helpers.get_google_type(light.DOMAIN, None) is not None
+ assert trait.OnOffTrait.supported(light.DOMAIN, 0, None)
trt_on = trait.OnOffTrait(hass, State('light.bla', STATE_ON), BASIC_CONFIG)
@@ -316,7 +332,7 @@ async def test_onoff_light(hass):
on_calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_ON)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': True})
+ {'on': True}, {})
assert len(on_calls) == 1
assert on_calls[0].data == {
ATTR_ENTITY_ID: 'light.bla',
@@ -325,7 +341,7 @@ async def test_onoff_light(hass):
off_calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_OFF)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': False})
+ {'on': False}, {})
assert len(off_calls) == 1
assert off_calls[0].data == {
ATTR_ENTITY_ID: 'light.bla',
@@ -334,7 +350,8 @@ async def test_onoff_light(hass):
async def test_onoff_media_player(hass):
"""Test OnOff trait support for media_player domain."""
- assert trait.OnOffTrait.supported(media_player.DOMAIN, 0)
+ assert helpers.get_google_type(media_player.DOMAIN, None) is not None
+ assert trait.OnOffTrait.supported(media_player.DOMAIN, 0, None)
trt_on = trait.OnOffTrait(hass, State('media_player.bla', STATE_ON),
BASIC_CONFIG)
@@ -355,7 +372,7 @@ async def test_onoff_media_player(hass):
on_calls = async_mock_service(hass, media_player.DOMAIN, SERVICE_TURN_ON)
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': True})
+ {'on': True}, {})
assert len(on_calls) == 1
assert on_calls[0].data == {
ATTR_ENTITY_ID: 'media_player.bla',
@@ -366,7 +383,7 @@ async def test_onoff_media_player(hass):
await trt_on.execute(
trait.COMMAND_ONOFF, BASIC_DATA,
- {'on': False})
+ {'on': False}, {})
assert len(off_calls) == 1
assert off_calls[0].data == {
ATTR_ENTITY_ID: 'media_player.bla',
@@ -375,13 +392,15 @@ async def test_onoff_media_player(hass):
async def test_onoff_climate(hass):
"""Test OnOff trait not supported for climate domain."""
+ assert helpers.get_google_type(climate.DOMAIN, None) is not None
assert not trait.OnOffTrait.supported(
- climate.DOMAIN, climate.SUPPORT_ON_OFF)
+ climate.DOMAIN, climate.SUPPORT_ON_OFF, None)
async def test_dock_vacuum(hass):
"""Test dock trait support for vacuum domain."""
- assert trait.DockTrait.supported(vacuum.DOMAIN, 0)
+ assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
+ assert trait.DockTrait.supported(vacuum.DOMAIN, 0, None)
trt = trait.DockTrait(hass, State('vacuum.bla', vacuum.STATE_IDLE),
BASIC_CONFIG)
@@ -395,7 +414,7 @@ async def test_dock_vacuum(hass):
calls = async_mock_service(hass, vacuum.DOMAIN,
vacuum.SERVICE_RETURN_TO_BASE)
await trt.execute(
- trait.COMMAND_DOCK, BASIC_DATA, {})
+ trait.COMMAND_DOCK, BASIC_DATA, {}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'vacuum.bla',
@@ -404,7 +423,8 @@ async def test_dock_vacuum(hass):
async def test_startstop_vacuum(hass):
"""Test startStop trait support for vacuum domain."""
- assert trait.StartStopTrait.supported(vacuum.DOMAIN, 0)
+ assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
+ assert trait.StartStopTrait.supported(vacuum.DOMAIN, 0, None)
trt = trait.StartStopTrait(hass, State('vacuum.bla', vacuum.STATE_PAUSED, {
ATTR_SUPPORTED_FEATURES: vacuum.SUPPORT_PAUSE,
@@ -419,7 +439,7 @@ async def test_startstop_vacuum(hass):
start_calls = async_mock_service(hass, vacuum.DOMAIN,
vacuum.SERVICE_START)
- await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {'start': True})
+ await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {'start': True}, {})
assert len(start_calls) == 1
assert start_calls[0].data == {
ATTR_ENTITY_ID: 'vacuum.bla',
@@ -427,7 +447,8 @@ async def test_startstop_vacuum(hass):
stop_calls = async_mock_service(hass, vacuum.DOMAIN,
vacuum.SERVICE_STOP)
- await trt.execute(trait.COMMAND_STARTSTOP, BASIC_DATA, {'start': False})
+ await trt.execute(
+ trait.COMMAND_STARTSTOP, BASIC_DATA, {'start': False}, {})
assert len(stop_calls) == 1
assert stop_calls[0].data == {
ATTR_ENTITY_ID: 'vacuum.bla',
@@ -435,7 +456,8 @@ async def test_startstop_vacuum(hass):
pause_calls = async_mock_service(hass, vacuum.DOMAIN,
vacuum.SERVICE_PAUSE)
- await trt.execute(trait.COMMAND_PAUSEUNPAUSE, BASIC_DATA, {'pause': True})
+ await trt.execute(
+ trait.COMMAND_PAUSEUNPAUSE, BASIC_DATA, {'pause': True}, {})
assert len(pause_calls) == 1
assert pause_calls[0].data == {
ATTR_ENTITY_ID: 'vacuum.bla',
@@ -443,38 +465,41 @@ async def test_startstop_vacuum(hass):
unpause_calls = async_mock_service(hass, vacuum.DOMAIN,
vacuum.SERVICE_START)
- await trt.execute(trait.COMMAND_PAUSEUNPAUSE, BASIC_DATA, {'pause': False})
+ await trt.execute(
+ trait.COMMAND_PAUSEUNPAUSE, BASIC_DATA, {'pause': False}, {})
assert len(unpause_calls) == 1
assert unpause_calls[0].data == {
ATTR_ENTITY_ID: 'vacuum.bla',
}
-async def test_color_spectrum_light(hass):
+async def test_color_setting_color_light(hass):
"""Test ColorSpectrum trait support for light domain."""
- assert not trait.ColorSpectrumTrait.supported(light.DOMAIN, 0)
- assert trait.ColorSpectrumTrait.supported(light.DOMAIN,
- light.SUPPORT_COLOR)
+ assert helpers.get_google_type(light.DOMAIN, None) is not None
+ assert not trait.ColorSettingTrait.supported(light.DOMAIN, 0, None)
+ assert trait.ColorSettingTrait.supported(light.DOMAIN,
+ light.SUPPORT_COLOR, None)
- trt = trait.ColorSpectrumTrait(hass, State('light.bla', STATE_ON, {
- light.ATTR_HS_COLOR: (0, 94),
+ trt = trait.ColorSettingTrait(hass, State('light.bla', STATE_ON, {
+ light.ATTR_HS_COLOR: (20, 94),
+ light.ATTR_BRIGHTNESS: 200,
+ ATTR_SUPPORTED_FEATURES: light.SUPPORT_COLOR,
}), BASIC_CONFIG)
assert trt.sync_attributes() == {
- 'colorModel': 'rgb'
+ 'colorModel': 'hsv'
}
assert trt.query_attributes() == {
'color': {
- 'spectrumRGB': 16715535
+ 'spectrumHsv': {
+ 'hue': 20,
+ 'saturation': 0.94,
+ 'value': 200 / 255,
+ }
}
}
- assert not trt.can_execute(trait.COMMAND_COLOR_ABSOLUTE, {
- 'color': {
- 'temperature': 400
- }
- })
assert trt.can_execute(trait.COMMAND_COLOR_ABSOLUTE, {
'color': {
'spectrumRGB': 16715792
@@ -486,34 +511,54 @@ async def test_color_spectrum_light(hass):
'color': {
'spectrumRGB': 1052927
}
- })
+ }, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'light.bla',
light.ATTR_HS_COLOR: (240, 93.725),
}
+ await trt.execute(trait.COMMAND_COLOR_ABSOLUTE, BASIC_DATA, {
+ 'color': {
+ 'spectrumHSV': {
+ 'hue': 100,
+ 'saturation': .50,
+ 'value': .20,
+ }
+ }
+ }, {})
+ assert len(calls) == 2
+ assert calls[1].data == {
+ ATTR_ENTITY_ID: 'light.bla',
+ light.ATTR_HS_COLOR: [100, 50],
+ light.ATTR_BRIGHTNESS: .2 * 255,
+ }
-async def test_color_temperature_light(hass):
+
+async def test_color_setting_temperature_light(hass):
"""Test ColorTemperature trait support for light domain."""
- assert not trait.ColorTemperatureTrait.supported(light.DOMAIN, 0)
- assert trait.ColorTemperatureTrait.supported(light.DOMAIN,
- light.SUPPORT_COLOR_TEMP)
+ assert helpers.get_google_type(light.DOMAIN, None) is not None
+ assert not trait.ColorSettingTrait.supported(light.DOMAIN, 0, None)
+ assert trait.ColorSettingTrait.supported(light.DOMAIN,
+ light.SUPPORT_COLOR_TEMP, None)
- trt = trait.ColorTemperatureTrait(hass, State('light.bla', STATE_ON, {
+ trt = trait.ColorSettingTrait(hass, State('light.bla', STATE_ON, {
light.ATTR_MIN_MIREDS: 200,
light.ATTR_COLOR_TEMP: 300,
light.ATTR_MAX_MIREDS: 500,
+ ATTR_SUPPORTED_FEATURES: light.SUPPORT_COLOR_TEMP,
}), BASIC_CONFIG)
assert trt.sync_attributes() == {
- 'temperatureMinK': 2000,
- 'temperatureMaxK': 5000,
+ 'colorTemperatureRange': {
+ 'temperatureMinK': 2000,
+ 'temperatureMaxK': 5000,
+ }
}
assert trt.query_attributes() == {
'color': {
- 'temperature': 3333
+ 'temperatureK': 3333
}
}
@@ -522,12 +567,6 @@ async def test_color_temperature_light(hass):
'temperature': 400
}
})
- assert not trt.can_execute(trait.COMMAND_COLOR_ABSOLUTE, {
- 'color': {
- 'spectrumRGB': 16715792
- }
- })
-
calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_ON)
with pytest.raises(helpers.SmartHomeError) as err:
@@ -535,14 +574,14 @@ async def test_color_temperature_light(hass):
'color': {
'temperature': 5555
}
- })
+ }, {})
assert err.value.code == const.ERR_VALUE_OUT_OF_RANGE
await trt.execute(trait.COMMAND_COLOR_ABSOLUTE, BASIC_DATA, {
'color': {
'temperature': 2857
}
- })
+ }, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'light.bla',
@@ -550,13 +589,14 @@ async def test_color_temperature_light(hass):
}
-async def test_color_temperature_light_bad_temp(hass):
+async def test_color_light_temperature_light_bad_temp(hass):
"""Test ColorTemperature trait support for light domain."""
- assert not trait.ColorTemperatureTrait.supported(light.DOMAIN, 0)
- assert trait.ColorTemperatureTrait.supported(light.DOMAIN,
- light.SUPPORT_COLOR_TEMP)
+ assert helpers.get_google_type(light.DOMAIN, None) is not None
+ assert not trait.ColorSettingTrait.supported(light.DOMAIN, 0, None)
+ assert trait.ColorSettingTrait.supported(light.DOMAIN,
+ light.SUPPORT_COLOR_TEMP, None)
- trt = trait.ColorTemperatureTrait(hass, State('light.bla', STATE_ON, {
+ trt = trait.ColorSettingTrait(hass, State('light.bla', STATE_ON, {
light.ATTR_MIN_MIREDS: 200,
light.ATTR_COLOR_TEMP: 0,
light.ATTR_MAX_MIREDS: 500,
@@ -568,7 +608,8 @@ async def test_color_temperature_light_bad_temp(hass):
async def test_scene_scene(hass):
"""Test Scene trait support for scene domain."""
- assert trait.SceneTrait.supported(scene.DOMAIN, 0)
+ assert helpers.get_google_type(scene.DOMAIN, None) is not None
+ assert trait.SceneTrait.supported(scene.DOMAIN, 0, None)
trt = trait.SceneTrait(hass, State('scene.bla', scene.STATE), BASIC_CONFIG)
assert trt.sync_attributes() == {}
@@ -576,7 +617,7 @@ async def test_scene_scene(hass):
assert trt.can_execute(trait.COMMAND_ACTIVATE_SCENE, {})
calls = async_mock_service(hass, scene.DOMAIN, SERVICE_TURN_ON)
- await trt.execute(trait.COMMAND_ACTIVATE_SCENE, BASIC_DATA, {})
+ await trt.execute(trait.COMMAND_ACTIVATE_SCENE, BASIC_DATA, {}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'scene.bla',
@@ -585,7 +626,8 @@ async def test_scene_scene(hass):
async def test_scene_script(hass):
"""Test Scene trait support for script domain."""
- assert trait.SceneTrait.supported(script.DOMAIN, 0)
+ assert helpers.get_google_type(script.DOMAIN, None) is not None
+ assert trait.SceneTrait.supported(script.DOMAIN, 0, None)
trt = trait.SceneTrait(hass, State('script.bla', STATE_OFF), BASIC_CONFIG)
assert trt.sync_attributes() == {}
@@ -593,7 +635,7 @@ async def test_scene_script(hass):
assert trt.can_execute(trait.COMMAND_ACTIVATE_SCENE, {})
calls = async_mock_service(hass, script.DOMAIN, SERVICE_TURN_ON)
- await trt.execute(trait.COMMAND_ACTIVATE_SCENE, BASIC_DATA, {})
+ await trt.execute(trait.COMMAND_ACTIVATE_SCENE, BASIC_DATA, {}, {})
# We don't wait till script execution is done.
await hass.async_block_till_done()
@@ -606,9 +648,10 @@ async def test_scene_script(hass):
async def test_temperature_setting_climate_onoff(hass):
"""Test TemperatureSetting trait support for climate domain - range."""
- assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0)
+ assert helpers.get_google_type(climate.DOMAIN, None) is not None
+ assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0, None)
assert trait.TemperatureSettingTrait.supported(
- climate.DOMAIN, climate.SUPPORT_OPERATION_MODE)
+ climate.DOMAIN, climate.SUPPORT_OPERATION_MODE, None)
hass.config.units.temperature_unit = TEMP_FAHRENHEIT
@@ -637,22 +680,23 @@ async def test_temperature_setting_climate_onoff(hass):
hass, climate.DOMAIN, SERVICE_TURN_ON)
await trt.execute(trait.COMMAND_THERMOSTAT_SET_MODE, BASIC_DATA, {
'thermostatMode': 'on',
- })
+ }, {})
assert len(calls) == 1
calls = async_mock_service(
hass, climate.DOMAIN, SERVICE_TURN_OFF)
await trt.execute(trait.COMMAND_THERMOSTAT_SET_MODE, BASIC_DATA, {
'thermostatMode': 'off',
- })
+ }, {})
assert len(calls) == 1
async def test_temperature_setting_climate_range(hass):
"""Test TemperatureSetting trait support for climate domain - range."""
- assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0)
+ assert helpers.get_google_type(climate.DOMAIN, None) is not None
+ assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0, None)
assert trait.TemperatureSettingTrait.supported(
- climate.DOMAIN, climate.SUPPORT_OPERATION_MODE)
+ climate.DOMAIN, climate.SUPPORT_OPERATION_MODE, None)
hass.config.units.temperature_unit = TEMP_FAHRENHEIT
@@ -696,7 +740,7 @@ async def test_temperature_setting_climate_range(hass):
trait.COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, BASIC_DATA, {
'thermostatTemperatureSetpointHigh': 25,
'thermostatTemperatureSetpointLow': 20,
- })
+ }, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'climate.bla',
@@ -708,7 +752,7 @@ async def test_temperature_setting_climate_range(hass):
hass, climate.DOMAIN, climate.SERVICE_SET_OPERATION_MODE)
await trt.execute(trait.COMMAND_THERMOSTAT_SET_MODE, BASIC_DATA, {
'thermostatMode': 'heatcool',
- })
+ }, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'climate.bla',
@@ -718,16 +762,17 @@ async def test_temperature_setting_climate_range(hass):
with pytest.raises(helpers.SmartHomeError) as err:
await trt.execute(
trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, BASIC_DATA,
- {'thermostatTemperatureSetpoint': -100})
+ {'thermostatTemperatureSetpoint': -100}, {})
assert err.value.code == const.ERR_VALUE_OUT_OF_RANGE
hass.config.units.temperature_unit = TEMP_CELSIUS
async def test_temperature_setting_climate_setpoint(hass):
"""Test TemperatureSetting trait support for climate domain - setpoint."""
- assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0)
+ assert helpers.get_google_type(climate.DOMAIN, None) is not None
+ assert not trait.TemperatureSettingTrait.supported(climate.DOMAIN, 0, None)
assert trait.TemperatureSettingTrait.supported(
- climate.DOMAIN, climate.SUPPORT_OPERATION_MODE)
+ climate.DOMAIN, climate.SUPPORT_OPERATION_MODE, None)
hass.config.units.temperature_unit = TEMP_CELSIUS
@@ -763,11 +808,11 @@ async def test_temperature_setting_climate_setpoint(hass):
with pytest.raises(helpers.SmartHomeError):
await trt.execute(
trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, BASIC_DATA,
- {'thermostatTemperatureSetpoint': -100})
+ {'thermostatTemperatureSetpoint': -100}, {})
await trt.execute(
trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, BASIC_DATA,
- {'thermostatTemperatureSetpoint': 19})
+ {'thermostatTemperatureSetpoint': 19}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'climate.bla',
@@ -815,7 +860,7 @@ async def test_temperature_setting_climate_setpoint_auto(hass):
await trt.execute(
trait.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT, BASIC_DATA,
- {'thermostatTemperatureSetpoint': 19})
+ {'thermostatTemperatureSetpoint': 19}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'climate.bla',
@@ -825,11 +870,13 @@ async def test_temperature_setting_climate_setpoint_auto(hass):
async def test_lock_unlock_lock(hass):
"""Test LockUnlock trait locking support for lock domain."""
- assert trait.LockUnlockTrait.supported(lock.DOMAIN, lock.SUPPORT_OPEN)
+ assert helpers.get_google_type(lock.DOMAIN, None) is not None
+ assert trait.LockUnlockTrait.supported(lock.DOMAIN, lock.SUPPORT_OPEN,
+ None)
trt = trait.LockUnlockTrait(hass,
State('lock.front_door', lock.STATE_UNLOCKED),
- BASIC_CONFIG)
+ PIN_CONFIG)
assert trt.sync_attributes() == {}
@@ -840,7 +887,26 @@ async def test_lock_unlock_lock(hass):
assert trt.can_execute(trait.COMMAND_LOCKUNLOCK, {'lock': True})
calls = async_mock_service(hass, lock.DOMAIN, lock.SERVICE_LOCK)
- await trt.execute(trait.COMMAND_LOCKUNLOCK, BASIC_DATA, {'lock': True})
+
+ # No challenge data
+ with pytest.raises(error.ChallengeNeeded) as err:
+ await trt.execute(
+ trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True}, {})
+ assert len(calls) == 0
+ assert err.code == const.ERR_CHALLENGE_NEEDED
+ assert err.challenge_type == const.CHALLENGE_PIN_NEEDED
+
+ # invalid pin
+ with pytest.raises(error.ChallengeNeeded) as err:
+ await trt.execute(
+ trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True},
+ {'pin': 9999})
+ assert len(calls) == 0
+ assert err.code == const.ERR_CHALLENGE_NEEDED
+ assert err.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
+
+ await trt.execute(trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': True},
+ {'pin': '1234'})
assert len(calls) == 1
assert calls[0].data == {
@@ -850,23 +916,13 @@ async def test_lock_unlock_lock(hass):
async def test_lock_unlock_unlock(hass):
"""Test LockUnlock trait unlocking support for lock domain."""
- assert trait.LockUnlockTrait.supported(lock.DOMAIN, lock.SUPPORT_OPEN)
+ assert helpers.get_google_type(lock.DOMAIN, None) is not None
+ assert trait.LockUnlockTrait.supported(lock.DOMAIN, lock.SUPPORT_OPEN,
+ None)
trt = trait.LockUnlockTrait(hass,
State('lock.front_door', lock.STATE_LOCKED),
- BASIC_CONFIG)
-
- assert trt.sync_attributes() == {}
-
- assert trt.query_attributes() == {
- 'isLocked': True
- }
-
- assert not trt.can_execute(trait.COMMAND_LOCKUNLOCK, {'lock': False})
-
- trt = trait.LockUnlockTrait(hass,
- State('lock.front_door', lock.STATE_LOCKED),
- UNSAFE_CONFIG)
+ PIN_CONFIG)
assert trt.sync_attributes() == {}
@@ -877,7 +933,26 @@ async def test_lock_unlock_unlock(hass):
assert trt.can_execute(trait.COMMAND_LOCKUNLOCK, {'lock': False})
calls = async_mock_service(hass, lock.DOMAIN, lock.SERVICE_UNLOCK)
- await trt.execute(trait.COMMAND_LOCKUNLOCK, BASIC_DATA, {'lock': False})
+
+ # No challenge data
+ with pytest.raises(error.ChallengeNeeded) as err:
+ await trt.execute(
+ trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, {})
+ assert len(calls) == 0
+ assert err.code == const.ERR_CHALLENGE_NEEDED
+ assert err.challenge_type == const.CHALLENGE_PIN_NEEDED
+
+ # invalid pin
+ with pytest.raises(error.ChallengeNeeded) as err:
+ await trt.execute(
+ trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False},
+ {'pin': 9999})
+ assert len(calls) == 0
+ assert err.code == const.ERR_CHALLENGE_NEEDED
+ assert err.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
+
+ await trt.execute(
+ trait.COMMAND_LOCKUNLOCK, PIN_DATA, {'lock': False}, {'pin': '1234'})
assert len(calls) == 1
assert calls[0].data == {
@@ -887,7 +962,9 @@ async def test_lock_unlock_unlock(hass):
async def test_fan_speed(hass):
"""Test FanSpeed trait speed control support for fan domain."""
- assert trait.FanSpeedTrait.supported(fan.DOMAIN, fan.SUPPORT_SET_SPEED)
+ assert helpers.get_google_type(fan.DOMAIN, None) is not None
+ assert trait.FanSpeedTrait.supported(fan.DOMAIN, fan.SUPPORT_SET_SPEED,
+ None)
trt = trait.FanSpeedTrait(
hass, State(
@@ -958,7 +1035,7 @@ async def test_fan_speed(hass):
calls = async_mock_service(hass, fan.DOMAIN, fan.SERVICE_SET_SPEED)
await trt.execute(
- trait.COMMAND_FANSPEED, BASIC_DATA, {'fanSpeed': 'medium'})
+ trait.COMMAND_FANSPEED, BASIC_DATA, {'fanSpeed': 'medium'}, {})
assert len(calls) == 1
assert calls[0].data == {
@@ -969,8 +1046,9 @@ async def test_fan_speed(hass):
async def test_modes(hass):
"""Test Mode trait."""
+ assert helpers.get_google_type(media_player.DOMAIN, None) is not None
assert trait.ModesTrait.supported(
- media_player.DOMAIN, media_player.SUPPORT_SELECT_SOURCE)
+ media_player.DOMAIN, media_player.SUPPORT_SELECT_SOURCE, None)
trt = trait.ModesTrait(
hass, State(
@@ -1046,7 +1124,7 @@ async def test_modes(hass):
trait.COMMAND_MODES, BASIC_DATA, {
'updateModeSettings': {
trt.HA_TO_GOOGLE.get(media_player.ATTR_INPUT_SOURCE): 'media'
- }})
+ }}, {})
assert len(calls) == 1
assert calls[0].data == {
@@ -1056,9 +1134,10 @@ async def test_modes(hass):
async def test_openclose_cover(hass):
- """Test cover trait."""
+ """Test OpenClose trait support for cover domain."""
+ assert helpers.get_google_type(cover.DOMAIN, None) is not None
assert trait.OpenCloseTrait.supported(cover.DOMAIN,
- cover.SUPPORT_SET_POSITION)
+ cover.SUPPORT_SET_POSITION, None)
# No position
trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, {
@@ -1069,15 +1148,24 @@ async def test_openclose_cover(hass):
'openPercent': 100
}
+ # No state
+ trt = trait.OpenCloseTrait(hass, State('cover.bla', STATE_UNKNOWN, {
+ }), BASIC_CONFIG)
+
+ assert trt.sync_attributes() == {}
+
+ with pytest.raises(helpers.SmartHomeError):
+ trt.query_attributes()
+
# Assumed state
trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, {
ATTR_ASSUMED_STATE: True,
}), BASIC_CONFIG)
assert trt.sync_attributes() == {}
- assert trt.query_attributes() == {
- 'openPercent': 50
- }
+
+ with pytest.raises(helpers.SmartHomeError):
+ trt.query_attributes()
trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, {
cover.ATTR_CURRENT_POSITION: 75
@@ -1092,9 +1180,99 @@ async def test_openclose_cover(hass):
hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
await trt.execute(
trait.COMMAND_OPENCLOSE, BASIC_DATA,
- {'openPercent': 50})
+ {'openPercent': 50}, {})
assert len(calls) == 1
assert calls[0].data == {
ATTR_ENTITY_ID: 'cover.bla',
cover.ATTR_POSITION: 50
}
+
+
+@pytest.mark.parametrize('device_class', (
+ cover.DEVICE_CLASS_DOOR,
+ cover.DEVICE_CLASS_GARAGE,
+))
+async def test_openclose_cover_secure(hass, device_class):
+ """Test OpenClose trait support for cover domain."""
+ assert helpers.get_google_type(cover.DOMAIN, device_class) is not None
+ assert trait.OpenCloseTrait.supported(
+ cover.DOMAIN, cover.SUPPORT_SET_POSITION, device_class)
+
+ trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, {
+ ATTR_DEVICE_CLASS: device_class,
+ cover.ATTR_CURRENT_POSITION: 75
+ }), PIN_CONFIG)
+
+ assert trt.sync_attributes() == {}
+ assert trt.query_attributes() == {
+ 'openPercent': 75
+ }
+
+ calls = async_mock_service(
+ hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION)
+
+ # No challenge data
+ with pytest.raises(error.ChallengeNeeded) as err:
+ await trt.execute(
+ trait.COMMAND_OPENCLOSE, PIN_DATA,
+ {'openPercent': 50}, {})
+ assert len(calls) == 0
+ assert err.code == const.ERR_CHALLENGE_NEEDED
+ assert err.challenge_type == const.CHALLENGE_PIN_NEEDED
+
+ # invalid pin
+ with pytest.raises(error.ChallengeNeeded) as err:
+ await trt.execute(
+ trait.COMMAND_OPENCLOSE, PIN_DATA,
+ {'openPercent': 50}, {'pin': '9999'})
+ assert len(calls) == 0
+ assert err.code == const.ERR_CHALLENGE_NEEDED
+ assert err.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
+
+ await trt.execute(
+ trait.COMMAND_OPENCLOSE, PIN_DATA,
+ {'openPercent': 50}, {'pin': '1234'})
+ assert len(calls) == 1
+ assert calls[0].data == {
+ ATTR_ENTITY_ID: 'cover.bla',
+ cover.ATTR_POSITION: 50
+ }
+
+
+@pytest.mark.parametrize('device_class', (
+ binary_sensor.DEVICE_CLASS_DOOR,
+ binary_sensor.DEVICE_CLASS_GARAGE_DOOR,
+ binary_sensor.DEVICE_CLASS_LOCK,
+ binary_sensor.DEVICE_CLASS_OPENING,
+ binary_sensor.DEVICE_CLASS_WINDOW,
+))
+async def test_openclose_binary_sensor(hass, device_class):
+ """Test OpenClose trait support for binary_sensor domain."""
+ assert helpers.get_google_type(
+ binary_sensor.DOMAIN, device_class) is not None
+ assert trait.OpenCloseTrait.supported(binary_sensor.DOMAIN,
+ 0, device_class)
+
+ trt = trait.OpenCloseTrait(hass, State('binary_sensor.test', STATE_ON, {
+ ATTR_DEVICE_CLASS: device_class,
+ }), BASIC_CONFIG)
+
+ assert trt.sync_attributes() == {
+ 'queryOnlyOpenClose': True,
+ }
+
+ assert trt.query_attributes() == {
+ 'openPercent': 100
+ }
+
+ trt = trait.OpenCloseTrait(hass, State('binary_sensor.test', STATE_OFF, {
+ ATTR_DEVICE_CLASS: device_class,
+ }), BASIC_CONFIG)
+
+ assert trt.sync_attributes() == {
+ 'queryOnlyOpenClose': True,
+ }
+
+ assert trt.query_attributes() == {
+ 'openPercent': 0
+ }
diff --git a/tests/components/google_translate/__init__.py b/tests/components/google_translate/__init__.py
new file mode 100644
index 00000000000..bc96e5028dd
--- /dev/null
+++ b/tests/components/google_translate/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Google Translate integration."""
diff --git a/tests/components/google/test_tts.py b/tests/components/google_translate/test_tts.py
similarity index 91%
rename from tests/components/google/test_tts.py
rename to tests/components/google_translate/test_tts.py
index 78bdd50b6d7..f5791085453 100644
--- a/tests/components/google/test_tts.py
+++ b/tests/components/google_translate/test_tts.py
@@ -47,7 +47,7 @@ class TestTTSGooglePlatform:
"""Test setup component."""
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
}
}
@@ -65,14 +65,14 @@ class TestTTSGooglePlatform:
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
}
}
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
- self.hass.services.call(tts.DOMAIN, 'google_say', {
+ self.hass.services.call(tts.DOMAIN, 'google_translate_say', {
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
})
self.hass.block_till_done()
@@ -93,7 +93,7 @@ class TestTTSGooglePlatform:
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
'language': 'de',
}
}
@@ -101,7 +101,7 @@ class TestTTSGooglePlatform:
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
- self.hass.services.call(tts.DOMAIN, 'google_say', {
+ self.hass.services.call(tts.DOMAIN, 'google_translate_say', {
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
})
self.hass.block_till_done()
@@ -121,7 +121,8 @@ class TestTTSGooglePlatform:
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
+ 'service_name': 'google_say',
}
}
@@ -148,14 +149,14 @@ class TestTTSGooglePlatform:
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
}
}
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
- self.hass.services.call(tts.DOMAIN, 'google_say', {
+ self.hass.services.call(tts.DOMAIN, 'google_translate_say', {
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
})
self.hass.block_till_done()
@@ -174,14 +175,14 @@ class TestTTSGooglePlatform:
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
}
}
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
- self.hass.services.call(tts.DOMAIN, 'google_say', {
+ self.hass.services.call(tts.DOMAIN, 'google_translate_say', {
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
})
self.hass.block_till_done()
@@ -205,7 +206,8 @@ class TestTTSGooglePlatform:
config = {
tts.DOMAIN: {
- 'platform': 'google',
+ 'platform': 'google_translate',
+ 'service_name': 'google_say',
}
}
diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py
index 577da5f33e6..fce93d0a774 100644
--- a/tests/components/gpslogger/test_init.py
+++ b/tests/components/gpslogger/test_init.py
@@ -26,31 +26,34 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture
-def gpslogger_client(loop, hass, aiohttp_client):
+async def gpslogger_client(loop, hass, aiohttp_client):
"""Mock client for GPSLogger (unauthenticated)."""
- assert loop.run_until_complete(async_setup_component(
- hass, 'persistent_notification', {}))
+ assert await async_setup_component(
+ hass, 'persistent_notification', {})
- assert loop.run_until_complete(async_setup_component(
+ assert await async_setup_component(
hass, DOMAIN, {
DOMAIN: {}
- }))
+ })
+
+ await hass.async_block_till_done()
with patch('homeassistant.components.device_tracker.update_config'):
- yield loop.run_until_complete(aiohttp_client(hass.http.app))
+ return await aiohttp_client(hass.http.app)
@pytest.fixture(autouse=True)
-def setup_zones(loop, hass):
+async def setup_zones(loop, hass):
"""Set up Zone config in HA."""
- assert loop.run_until_complete(async_setup_component(
+ assert await async_setup_component(
hass, zone.DOMAIN, {
'zone': {
'name': 'Home',
'latitude': HOME_LATITUDE,
'longitude': HOME_LONGITUDE,
'radius': 100,
- }}))
+ }})
+ await hass.async_block_till_done()
@pytest.fixture
@@ -66,6 +69,7 @@ async def webhook_id(hass, gpslogger_client):
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ await hass.async_block_till_done()
return result['result'].data['webhook_id']
diff --git a/tests/components/group/test_notify.py b/tests/components/group/test_notify.py
index 9412e9f95a4..72993c9006b 100644
--- a/tests/components/group/test_notify.py
+++ b/tests/components/group/test_notify.py
@@ -29,7 +29,7 @@ class TestNotifyGroup(unittest.TestCase):
return self.service1
return self.service2
- with assert_setup_component(2), \
+ with assert_setup_component(2, notify.DOMAIN), \
patch.object(demo, 'get_service', mock_get_service):
setup_component(self.hass, notify.DOMAIN, {
'notify': [{
diff --git a/tests/components/hangouts/test_config_flow.py b/tests/components/hangouts/test_config_flow.py
index af9bb018919..becb981d68d 100644
--- a/tests/components/hangouts/test_config_flow.py
+++ b/tests/components/hangouts/test_config_flow.py
@@ -19,6 +19,20 @@ async def test_flow_works(hass, aioclient_mock):
assert result['title'] == 'test@test.com'
+async def test_flow_works_with_authcode(hass, aioclient_mock):
+ """Test config flow without 2fa."""
+ flow = config_flow.HangoutsFlowHandler()
+
+ flow.hass = hass
+
+ with patch('hangups.get_auth'):
+ result = await flow.async_step_user(
+ {'email': 'test@test.com', 'password': '1232456',
+ 'authorization_code': 'c29tZXJhbmRvbXN0cmluZw=='})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['title'] == 'test@test.com'
+
+
async def test_flow_works_with_2fa(hass, aioclient_mock):
"""Test config flow with 2fa."""
from homeassistant.components.hangouts.hangups_utils import Google2FAError
diff --git a/tests/components/hassio/test_addon_panel.py b/tests/components/hassio/test_addon_panel.py
new file mode 100644
index 00000000000..05915218659
--- /dev/null
+++ b/tests/components/hassio/test_addon_panel.py
@@ -0,0 +1,128 @@
+"""Test add-on panel."""
+from unittest.mock import patch, Mock
+
+import pytest
+
+from homeassistant.setup import async_setup_component
+from homeassistant.const import HTTP_HEADER_HA_AUTH
+
+from tests.common import mock_coro
+from . import API_PASSWORD
+
+
+@pytest.fixture(autouse=True)
+def mock_all(aioclient_mock):
+ """Mock all setup requests."""
+ aioclient_mock.post(
+ "http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
+ aioclient_mock.get(
+ "http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
+ aioclient_mock.post(
+ "http://127.0.0.1/supervisor/options", json={'result': 'ok'})
+ aioclient_mock.get(
+ "http://127.0.0.1/homeassistant/info", json={
+ 'result': 'ok', 'data': {'last_version': '10.0'}})
+
+
+async def test_hassio_addon_panel_startup(hass, aioclient_mock, hassio_env):
+ """Test startup and panel setup after event."""
+ aioclient_mock.get(
+ "http://127.0.0.1/ingress/panels", json={
+ 'result': 'ok', 'data': {'panels': {
+ "test1": {
+ "enable": True,
+ "title": "Test",
+ "icon": "mdi:test",
+ "admin": False
+ },
+ "test2": {
+ "enable": False,
+ "title": "Test 2",
+ "icon": "mdi:test2",
+ "admin": True
+ },
+ }}})
+
+ assert aioclient_mock.call_count == 0
+
+ with patch(
+ 'homeassistant.components.hassio.addon_panel._register_panel',
+ Mock(return_value=mock_coro())
+ ) as mock_panel:
+ await async_setup_component(hass, 'hassio', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+ await hass.async_block_till_done()
+
+ assert aioclient_mock.call_count == 2
+ assert mock_panel.called
+ mock_panel.assert_called_with(
+ hass, 'test1', {
+ 'enable': True, 'title': 'Test',
+ 'icon': 'mdi:test', 'admin': False
+ })
+
+
+async def test_hassio_addon_panel_api(hass, aioclient_mock, hassio_env,
+ hass_client):
+ """Test panel api after event."""
+ aioclient_mock.get(
+ "http://127.0.0.1/ingress/panels", json={
+ 'result': 'ok', 'data': {'panels': {
+ "test1": {
+ "enable": True,
+ "title": "Test",
+ "icon": "mdi:test",
+ "admin": False
+ },
+ "test2": {
+ "enable": False,
+ "title": "Test 2",
+ "icon": "mdi:test2",
+ "admin": True
+ },
+ }}})
+
+ assert aioclient_mock.call_count == 0
+
+ with patch(
+ 'homeassistant.components.hassio.addon_panel._register_panel',
+ Mock(return_value=mock_coro())
+ ) as mock_panel:
+ await async_setup_component(hass, 'hassio', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+ await hass.async_block_till_done()
+
+ assert aioclient_mock.call_count == 2
+ assert mock_panel.called
+ mock_panel.assert_called_with(
+ hass, 'test1', {
+ 'enable': True, 'title': 'Test',
+ 'icon': 'mdi:test', 'admin': False
+ })
+
+ hass_client = await hass_client()
+
+ resp = await hass_client.post(
+ '/api/hassio_push/panel/test2', headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ })
+ assert resp.status == 400
+
+ resp = await hass_client.post(
+ '/api/hassio_push/panel/test1', headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ })
+ assert resp.status == 200
+ assert mock_panel.call_count == 2
+
+ mock_panel.assert_called_with(
+ hass, 'test1', {
+ 'enable': True, 'title': 'Test',
+ 'icon': 'mdi:test', 'admin': False
+ })
diff --git a/tests/components/hassio/test_handler.py b/tests/components/hassio/test_handler.py
index 3e7b9e95d92..372d567c021 100644
--- a/tests/components/hassio/test_handler.py
+++ b/tests/components/hassio/test_handler.py
@@ -105,3 +105,23 @@ async def test_api_retrieve_discovery(hassio_handler, aioclient_mock):
data = await hassio_handler.retrieve_discovery_messages()
assert data['discovery'][-1]['service'] == "mqtt"
assert aioclient_mock.call_count == 1
+
+
+async def test_api_ingress_panels(hassio_handler, aioclient_mock):
+ """Test setup with API Ingress panels."""
+ aioclient_mock.get(
+ "http://127.0.0.1/ingress/panels", json={'result': 'ok', 'data': {
+ "panels": {
+ "slug": {
+ "enable": True,
+ "title": "Test",
+ "icon": "mdi:test",
+ "admin": False
+ }
+ }
+ }})
+
+ data = await hassio_handler.get_ingress_panels()
+ assert aioclient_mock.call_count == 1
+ assert data['panels']
+ assert "slug" in data['panels']
diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py
index f1f148f8495..7b8fad3ec09 100644
--- a/tests/components/hassio/test_init.py
+++ b/tests/components/hassio/test_init.py
@@ -31,6 +31,9 @@ def mock_all(aioclient_mock):
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
+ aioclient_mock.get(
+ "http://127.0.0.1/ingress/panels", json={
+ 'result': 'ok', 'data': {'panels': {}}})
@asyncio.coroutine
@@ -40,7 +43,7 @@ def test_setup_api_ping(hass, aioclient_mock):
result = yield from async_setup_component(hass, 'hassio', {})
assert result
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
assert hass.components.hassio.get_homeassistant_version() == "10.0"
assert hass.components.hassio.is_hassio()
@@ -79,7 +82,7 @@ def test_setup_api_push_api_data(hass, aioclient_mock):
})
assert result
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['port'] == 9999
assert aioclient_mock.mock_calls[1][2]['watchdog']
@@ -98,7 +101,7 @@ def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
})
assert result
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['port'] == 9999
assert not aioclient_mock.mock_calls[1][2]['watchdog']
@@ -114,7 +117,7 @@ async def test_setup_api_push_api_data_default(hass, aioclient_mock,
})
assert result
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
refresh_token = aioclient_mock.mock_calls[1][2]['refresh_token']
@@ -174,7 +177,7 @@ async def test_setup_api_existing_hassio_user(hass, aioclient_mock,
})
assert result
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
assert aioclient_mock.mock_calls[1][2]['refresh_token'] == token.token
@@ -192,7 +195,7 @@ def test_setup_core_push_timezone(hass, aioclient_mock):
})
assert result
- assert aioclient_mock.call_count == 4
+ assert aioclient_mock.call_count == 5
assert aioclient_mock.mock_calls[2][2]['timezone'] == "testzone"
@@ -206,7 +209,7 @@ def test_setup_hassio_no_additional_data(hass, aioclient_mock):
})
assert result
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
assert aioclient_mock.mock_calls[-1][3]['X-Hassio-Key'] == "123456"
@@ -285,14 +288,14 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
'hassio', 'addon_stdin', {'addon': 'test', 'input': 'test'})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 5
+ assert aioclient_mock.call_count == 6
assert aioclient_mock.mock_calls[-1][2] == 'test'
yield from hass.services.async_call('hassio', 'host_shutdown', {})
yield from hass.services.async_call('hassio', 'host_reboot', {})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 7
+ assert aioclient_mock.call_count == 8
yield from hass.services.async_call('hassio', 'snapshot_full', {})
yield from hass.services.async_call('hassio', 'snapshot_partial', {
@@ -302,7 +305,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 9
+ assert aioclient_mock.call_count == 10
assert aioclient_mock.mock_calls[-1][2] == {
'addons': ['test'], 'folders': ['ssl'], 'password': "123456"}
@@ -318,7 +321,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
})
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 11
+ assert aioclient_mock.call_count == 12
assert aioclient_mock.mock_calls[-1][2] == {
'addons': ['test'], 'folders': ['ssl'], 'homeassistant': False,
'password': "123456"
@@ -338,12 +341,12 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
yield from hass.services.async_call('homeassistant', 'stop')
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 2
+ assert aioclient_mock.call_count == 3
yield from hass.services.async_call('homeassistant', 'check_config')
yield from hass.async_block_till_done()
- assert aioclient_mock.call_count == 2
+ assert aioclient_mock.call_count == 3
with patch(
'homeassistant.config.async_check_ha_config_file',
@@ -353,4 +356,4 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
yield from hass.async_block_till_done()
assert mock_check_config.called
- assert aioclient_mock.call_count == 3
+ assert aioclient_mock.call_count == 4
diff --git a/tests/components/heos/__init__.py b/tests/components/heos/__init__.py
new file mode 100644
index 00000000000..3a774529c69
--- /dev/null
+++ b/tests/components/heos/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Heos component."""
diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py
new file mode 100644
index 00000000000..496f143d51f
--- /dev/null
+++ b/tests/components/heos/conftest.py
@@ -0,0 +1,125 @@
+"""Configuration for HEOS tests."""
+from typing import Dict, Sequence
+
+from asynctest.mock import Mock, patch as patch
+from pyheos import Dispatcher, HeosPlayer, HeosSource, InputSource, const
+import pytest
+
+from homeassistant.components.heos import DOMAIN
+from homeassistant.const import CONF_HOST
+
+from tests.common import MockConfigEntry
+
+
+@pytest.fixture(name="config_entry")
+def config_entry_fixture():
+ """Create a mock HEOS config entry."""
+ return MockConfigEntry(domain=DOMAIN, data={CONF_HOST: '127.0.0.1'},
+ title='Controller (127.0.0.1)')
+
+
+@pytest.fixture(name="controller")
+def controller_fixture(players, favorites, input_sources, dispatcher):
+ """Create a mock Heos controller fixture."""
+ with patch("pyheos.Heos", autospec=True) as mock:
+ mock_heos = mock.return_value
+ mock_heos.dispatcher = dispatcher
+ mock_heos.get_players.return_value = players
+ mock_heos.players = players
+ mock_heos.get_favorites.return_value = favorites
+ mock_heos.get_input_sources.return_value = input_sources
+ mock_heos.is_signed_in = True
+ mock_heos.signed_in_username = "user@user.com"
+ yield mock_heos
+
+
+@pytest.fixture(name="config")
+def config_fixture():
+ """Create hass config fixture."""
+ return {
+ DOMAIN: {CONF_HOST: '127.0.0.1'}
+ }
+
+
+@pytest.fixture(name="players")
+def player_fixture(dispatcher):
+ """Create a mock HeosPlayer."""
+ player = Mock(HeosPlayer)
+ player.heos.dispatcher = dispatcher
+ player.player_id = 1
+ player.name = "Test Player"
+ player.model = "Test Model"
+ player.version = "1.0.0"
+ player.is_muted = False
+ player.available = True
+ player.state = const.PLAY_STATE_STOP
+ player.ip_address = "127.0.0.1"
+ player.network = "wired"
+ player.shuffle = False
+ player.repeat = const.REPEAT_OFF
+ player.volume = 25
+ player.now_playing_media.supported_controls = const.CONTROLS_ALL
+ player.now_playing_media.album_id = 1
+ player.now_playing_media.queue_id = 1
+ player.now_playing_media.source_id = 1
+ player.now_playing_media.station = "Station Name"
+ player.now_playing_media.type = "Station"
+ player.now_playing_media.album = "Album"
+ player.now_playing_media.artist = "Artist"
+ player.now_playing_media.media_id = "1"
+ player.now_playing_media.duration = None
+ player.now_playing_media.current_position = None
+ player.now_playing_media.image_url = "http://"
+ player.now_playing_media.song = "Song"
+ return {player.player_id: player}
+
+
+@pytest.fixture(name="favorites")
+def favorites_fixture() -> Dict[int, HeosSource]:
+ """Create favorites fixture."""
+ station = Mock(HeosSource)
+ station.type = const.TYPE_STATION
+ station.name = "Today's Hits Radio"
+ station.media_id = '123456789'
+ radio = Mock(HeosSource)
+ radio.type = const.TYPE_STATION
+ radio.name = "Classical MPR (Classical Music)"
+ radio.media_id = 's1234'
+ return {
+ 1: station,
+ 2: radio
+ }
+
+
+@pytest.fixture(name="input_sources")
+def input_sources_fixture() -> Sequence[InputSource]:
+ """Create a set of input sources for testing."""
+ source = Mock(InputSource)
+ source.player_id = 1
+ source.input_name = const.INPUT_AUX_IN_1
+ source.name = "HEOS Drive - Line In 1"
+ return [source]
+
+
+@pytest.fixture(name="dispatcher")
+def dispatcher_fixture() -> Dispatcher:
+ """Create a dispatcher for testing."""
+ return Dispatcher()
+
+
+@pytest.fixture(name="discovery_data")
+def discovery_data_fixture() -> dict:
+ """Return mock discovery data for testing."""
+ return {
+ 'host': '127.0.0.1',
+ 'manufacturer': 'Denon',
+ 'model_name': 'HEOS Drive',
+ 'model_number': 'DWSA-10 4.0',
+ 'name': 'Office',
+ 'port': 60006,
+ 'serial': None,
+ 'ssdp_description':
+ 'http://127.0.0.1:60006/upnp/desc/aios_device/aios_device.xml',
+ 'udn': 'uuid:e61de70c-2250-1c22-0080-0005cdf512be',
+ 'upnp_device_type': 'urn:schemas-denon-com:device:AiosDevice:1'
+ }
diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py
new file mode 100644
index 00000000000..ade0100dbd6
--- /dev/null
+++ b/tests/components/heos/test_config_flow.py
@@ -0,0 +1,108 @@
+"""Tests for the Heos config flow module."""
+import asyncio
+
+from homeassistant import data_entry_flow
+from homeassistant.components.heos.config_flow import HeosFlowHandler
+from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN
+from homeassistant.const import CONF_HOST, CONF_NAME
+
+
+async def test_flow_aborts_already_setup(hass, config_entry):
+ """Test flow aborts when entry already setup."""
+ config_entry.add_to_hass(hass)
+ flow = HeosFlowHandler()
+ flow.hass = hass
+ result = await flow.async_step_user()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'already_setup'
+
+
+async def test_no_host_shows_form(hass):
+ """Test form is shown when host not provided."""
+ flow = HeosFlowHandler()
+ flow.hass = hass
+ result = await flow.async_step_user()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'user'
+ assert result['errors'] == {}
+
+
+async def test_cannot_connect_shows_error_form(hass, controller):
+ """Test form is shown with error when cannot connect."""
+ flow = HeosFlowHandler()
+ flow.hass = hass
+
+ errors = [ConnectionError, asyncio.TimeoutError]
+ for error in errors:
+ controller.connect.side_effect = error
+ result = await flow.async_step_user({CONF_HOST: '127.0.0.1'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'user'
+ assert result['errors'][CONF_HOST] == 'connection_failure'
+ assert controller.connect.call_count == 1
+ assert controller.disconnect.call_count == 1
+ controller.connect.reset_mock()
+ controller.disconnect.reset_mock()
+
+
+async def test_create_entry_when_host_valid(hass, controller):
+ """Test result type is create entry when host is valid."""
+ flow = HeosFlowHandler()
+ flow.hass = hass
+ data = {CONF_HOST: '127.0.0.1'}
+ result = await flow.async_step_user(data)
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['title'] == 'Controller (127.0.0.1)'
+ assert result['data'] == data
+ assert controller.connect.call_count == 1
+ assert controller.disconnect.call_count == 1
+
+
+async def test_create_entry_when_friendly_name_valid(hass, controller):
+ """Test result type is create entry when friendly name is valid."""
+ hass.data[DATA_DISCOVERED_HOSTS] = {"Office (127.0.0.1)": "127.0.0.1"}
+ flow = HeosFlowHandler()
+ flow.hass = hass
+ data = {CONF_HOST: "Office (127.0.0.1)"}
+ result = await flow.async_step_user(data)
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['title'] == 'Controller (127.0.0.1)'
+ assert result['data'] == {CONF_HOST: "127.0.0.1"}
+ assert controller.connect.call_count == 1
+ assert controller.disconnect.call_count == 1
+ assert DATA_DISCOVERED_HOSTS not in hass.data
+
+
+async def test_discovery_shows_create_form(hass, controller, discovery_data):
+ """Test discovery shows form to confirm setup and subsequent abort."""
+ await hass.config_entries.flow.async_init(
+ DOMAIN, context={'source': 'discovery'},
+ data=discovery_data)
+ await hass.async_block_till_done()
+ assert len(hass.config_entries.flow.async_progress()) == 1
+ assert hass.data[DATA_DISCOVERED_HOSTS] == {
+ "Office (127.0.0.1)": "127.0.0.1"
+ }
+
+ discovery_data[CONF_HOST] = "127.0.0.2"
+ discovery_data[CONF_NAME] = "Bedroom"
+ await hass.config_entries.flow.async_init(
+ DOMAIN, context={'source': 'discovery'},
+ data=discovery_data)
+ await hass.async_block_till_done()
+ assert len(hass.config_entries.flow.async_progress()) == 1
+ assert hass.data[DATA_DISCOVERED_HOSTS] == {
+ "Office (127.0.0.1)": "127.0.0.1",
+ "Bedroom (127.0.0.2)": "127.0.0.2"
+ }
+
+
+async def test_disovery_flow_aborts_already_setup(
+ hass, controller, discovery_data, config_entry):
+ """Test discovery flow aborts when entry already setup."""
+ config_entry.add_to_hass(hass)
+ flow = HeosFlowHandler()
+ flow.hass = hass
+ result = await flow.async_step_discovery(discovery_data)
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'already_setup'
diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py
new file mode 100644
index 00000000000..408b2f7d088
--- /dev/null
+++ b/tests/components/heos/test_init.py
@@ -0,0 +1,168 @@
+"""Tests for the init module."""
+import asyncio
+
+from asynctest import patch
+from pyheos import CommandError, const
+import pytest
+
+from homeassistant.components.heos import async_setup_entry, async_unload_entry
+from homeassistant.components.heos.const import (
+ DATA_CONTROLLER, DATA_SOURCE_MANAGER, DOMAIN)
+from homeassistant.components.media_player.const import (
+ DOMAIN as MEDIA_PLAYER_DOMAIN)
+from homeassistant.const import CONF_HOST
+from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.setup import async_setup_component
+
+
+async def test_async_setup_creates_entry(hass, config):
+ """Test component setup creates entry from config."""
+ assert await async_setup_component(hass, DOMAIN, config)
+ await hass.async_block_till_done()
+ entries = hass.config_entries.async_entries(DOMAIN)
+ assert len(entries) == 1
+ entry = entries[0]
+ assert entry.title == 'Controller (127.0.0.1)'
+ assert entry.data == {CONF_HOST: '127.0.0.1'}
+
+
+async def test_async_setup_updates_entry(hass, config_entry, config):
+ """Test component setup updates entry from config."""
+ config[DOMAIN][CONF_HOST] = '127.0.0.2'
+ config_entry.add_to_hass(hass)
+ assert await async_setup_component(hass, DOMAIN, config)
+ await hass.async_block_till_done()
+ entries = hass.config_entries.async_entries(DOMAIN)
+ assert len(entries) == 1
+ entry = entries[0]
+ assert entry.title == 'Controller (127.0.0.2)'
+ assert entry.data == {CONF_HOST: '127.0.0.2'}
+
+
+async def test_async_setup_returns_true(hass, config_entry, config):
+ """Test component setup from config."""
+ config_entry.add_to_hass(hass)
+ assert await async_setup_component(hass, DOMAIN, config)
+ await hass.async_block_till_done()
+ entries = hass.config_entries.async_entries(DOMAIN)
+ assert len(entries) == 1
+ assert entries[0] == config_entry
+
+
+async def test_async_setup_no_config_returns_true(hass, config_entry):
+ """Test component setup from entry only."""
+ config_entry.add_to_hass(hass)
+ assert await async_setup_component(hass, DOMAIN, {})
+ await hass.async_block_till_done()
+ entries = hass.config_entries.async_entries(DOMAIN)
+ assert len(entries) == 1
+ assert entries[0] == config_entry
+
+
+async def test_async_setup_entry_loads_platforms(
+ hass, config_entry, controller, input_sources, favorites):
+ """Test load connects to heos, retrieves players, and loads platforms."""
+ config_entry.add_to_hass(hass)
+ with patch.object(
+ hass.config_entries, 'async_forward_entry_setup') as forward_mock:
+ assert await async_setup_entry(hass, config_entry)
+ # Assert platforms loaded
+ await hass.async_block_till_done()
+ assert forward_mock.call_count == 1
+ assert controller.connect.call_count == 1
+ assert controller.get_players.call_count == 1
+ assert controller.get_favorites.call_count == 1
+ assert controller.get_input_sources.call_count == 1
+ controller.disconnect.assert_not_called()
+ assert hass.data[DOMAIN][DATA_CONTROLLER] == controller
+ assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players
+ assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].favorites == favorites
+ assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].inputs == input_sources
+
+
+async def test_async_setup_entry_not_signed_in_loads_platforms(
+ hass, config_entry, controller, input_sources, caplog):
+ """Test setup does not retrieve favorites when not logged in."""
+ config_entry.add_to_hass(hass)
+ controller.is_signed_in = False
+ controller.signed_in_username = None
+ with patch.object(
+ hass.config_entries, 'async_forward_entry_setup') as forward_mock:
+ assert await async_setup_entry(hass, config_entry)
+ # Assert platforms loaded
+ await hass.async_block_till_done()
+ assert forward_mock.call_count == 1
+ assert controller.connect.call_count == 1
+ assert controller.get_players.call_count == 1
+ assert controller.get_favorites.call_count == 0
+ assert controller.get_input_sources.call_count == 1
+ controller.disconnect.assert_not_called()
+ assert hass.data[DOMAIN][DATA_CONTROLLER] == controller
+ assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players
+ assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].favorites == {}
+ assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].inputs == input_sources
+ assert "127.0.0.1 is not logged in to your HEOS account and will be " \
+ "unable to retrieve your favorites" in caplog.text
+
+
+async def test_async_setup_entry_connect_failure(
+ hass, config_entry, controller):
+ """Connection failure raises ConfigEntryNotReady."""
+ config_entry.add_to_hass(hass)
+ errors = [ConnectionError, asyncio.TimeoutError]
+ for error in errors:
+ controller.connect.side_effect = error
+ with pytest.raises(ConfigEntryNotReady):
+ await async_setup_entry(hass, config_entry)
+ await hass.async_block_till_done()
+ assert controller.connect.call_count == 1
+ assert controller.disconnect.call_count == 1
+ controller.connect.reset_mock()
+ controller.disconnect.reset_mock()
+
+
+async def test_async_setup_entry_player_failure(
+ hass, config_entry, controller):
+ """Failure to retrieve players/sources raises ConfigEntryNotReady."""
+ config_entry.add_to_hass(hass)
+ errors = [ConnectionError, asyncio.TimeoutError]
+ for error in errors:
+ controller.get_players.side_effect = error
+ with pytest.raises(ConfigEntryNotReady):
+ await async_setup_entry(hass, config_entry)
+ await hass.async_block_till_done()
+ assert controller.connect.call_count == 1
+ assert controller.disconnect.call_count == 1
+ controller.connect.reset_mock()
+ controller.disconnect.reset_mock()
+
+
+async def test_unload_entry(hass, config_entry, controller):
+ """Test entries are unloaded correctly."""
+ hass.data[DOMAIN] = {DATA_CONTROLLER: controller}
+ with patch.object(hass.config_entries, 'async_forward_entry_unload',
+ return_value=True) as unload:
+ assert await async_unload_entry(hass, config_entry)
+ await hass.async_block_till_done()
+ assert controller.disconnect.call_count == 1
+ assert unload.call_count == 1
+ assert DOMAIN not in hass.data
+
+
+async def test_update_sources_retry(hass, config_entry, config, controller,
+ caplog):
+ """Test update sources retries on failures to max attempts."""
+ config_entry.add_to_hass(hass)
+ assert await async_setup_component(hass, DOMAIN, config)
+ controller.get_favorites.reset_mock()
+ controller.get_input_sources.reset_mock()
+ source_manager = hass.data[DOMAIN][DATA_SOURCE_MANAGER]
+ source_manager.retry_delay = 0
+ source_manager.max_retry_attempts = 1
+ controller.get_favorites.side_effect = CommandError("Test", "test", 0)
+ controller.dispatcher.send(
+ const.SIGNAL_CONTROLLER_EVENT, const.EVENT_SOURCES_CHANGED)
+ # Wait until it's finished
+ while "Unable to update sources" not in caplog.text:
+ await asyncio.sleep(0.1)
+ assert controller.get_favorites.call_count == 2
diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py
new file mode 100644
index 00000000000..0870f82b3ff
--- /dev/null
+++ b/tests/components/heos/test_media_player.py
@@ -0,0 +1,417 @@
+"""Tests for the Heos Media Player platform."""
+import asyncio
+
+from pyheos import const, CommandError
+
+from homeassistant.components.heos import media_player
+from homeassistant.components.heos.const import (
+ DATA_SOURCE_MANAGER, DOMAIN, SIGNAL_HEOS_SOURCES_UPDATED)
+from homeassistant.components.media_player.const import (
+ ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_ALBUM_NAME,
+ ATTR_MEDIA_ARTIST, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE,
+ ATTR_MEDIA_DURATION, ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT,
+ ATTR_MEDIA_SHUFFLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_VOLUME_LEVEL,
+ ATTR_MEDIA_VOLUME_MUTED, DOMAIN as MEDIA_PLAYER_DOMAIN, MEDIA_TYPE_MUSIC,
+ SERVICE_CLEAR_PLAYLIST, SERVICE_SELECT_SOURCE, SUPPORT_NEXT_TRACK,
+ SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_SUPPORTED_FEATURES,
+ SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
+ SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_SHUFFLE_SET,
+ SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, STATE_IDLE, STATE_PLAYING,
+ STATE_UNAVAILABLE)
+from homeassistant.setup import async_setup_component
+
+
+async def setup_platform(hass, config_entry, config):
+ """Set up the media player platform for testing."""
+ config_entry.add_to_hass(hass)
+ assert await async_setup_component(hass, DOMAIN, config)
+ await hass.async_block_till_done()
+
+
+async def test_async_setup_platform():
+ """Test setup platform does nothing (it uses config entries)."""
+ await media_player.async_setup_platform(None, None, None)
+
+
+async def test_state_attributes(hass, config_entry, config, controller):
+ """Tests the state attributes."""
+ await setup_platform(hass, config_entry, config)
+ state = hass.states.get('media_player.test_player')
+ assert state.state == STATE_IDLE
+ assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.25
+ assert not state.attributes[ATTR_MEDIA_VOLUME_MUTED]
+ assert state.attributes[ATTR_MEDIA_CONTENT_ID] == "1"
+ assert state.attributes[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
+ assert ATTR_MEDIA_DURATION not in state.attributes
+ assert ATTR_MEDIA_POSITION not in state.attributes
+ assert state.attributes[ATTR_MEDIA_TITLE] == "Song"
+ assert state.attributes[ATTR_MEDIA_ARTIST] == "Artist"
+ assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "Album"
+ assert not state.attributes[ATTR_MEDIA_SHUFFLE]
+ assert state.attributes['media_album_id'] == 1
+ assert state.attributes['media_queue_id'] == 1
+ assert state.attributes['media_source_id'] == 1
+ assert state.attributes['media_station'] == "Station Name"
+ assert state.attributes['media_type'] == "Station"
+ assert state.attributes[ATTR_FRIENDLY_NAME] == "Test Player"
+ assert state.attributes[ATTR_SUPPORTED_FEATURES] == \
+ SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_STOP | SUPPORT_NEXT_TRACK | \
+ SUPPORT_PREVIOUS_TRACK | media_player.BASE_SUPPORTED_FEATURES
+ assert ATTR_INPUT_SOURCE not in state.attributes
+ assert state.attributes[ATTR_INPUT_SOURCE_LIST] == \
+ hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list
+
+
+async def test_updates_start_from_signals(
+ hass, config_entry, config, controller, favorites):
+ """Tests dispatched signals update player."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+
+ # Test player does not update for other players
+ player.state = const.PLAY_STATE_PLAY
+ player.heos.dispatcher.send(
+ const.SIGNAL_PLAYER_EVENT, 2,
+ const.EVENT_PLAYER_STATE_CHANGED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.state == STATE_IDLE
+
+ # Test player_update standard events
+ player.state = const.PLAY_STATE_PLAY
+ player.heos.dispatcher.send(
+ const.SIGNAL_PLAYER_EVENT, player.player_id,
+ const.EVENT_PLAYER_STATE_CHANGED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.state == STATE_PLAYING
+
+ # Test player_update progress events
+ player.now_playing_media.duration = 360000
+ player.now_playing_media.current_position = 1000
+ player.heos.dispatcher.send(
+ const.SIGNAL_PLAYER_EVENT, player.player_id,
+ const.EVENT_PLAYER_NOW_PLAYING_PROGRESS)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] is not None
+ assert state.attributes[ATTR_MEDIA_DURATION] == 360
+ assert state.attributes[ATTR_MEDIA_POSITION] == 1
+
+ # Test controller player change updates
+ player.available = False
+ player.heos.dispatcher.send(
+ const.SIGNAL_CONTROLLER_EVENT, const.EVENT_PLAYERS_CHANGED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.state == STATE_UNAVAILABLE
+
+ # Test heos events update
+ player.available = True
+ player.heos.dispatcher.send(
+ const.SIGNAL_HEOS_EVENT, const.EVENT_CONNECTED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.state == STATE_PLAYING
+
+
+async def test_updates_from_sources_updated(
+ hass, config_entry, config, controller, input_sources):
+ """Tests player updates from changes in sources list."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ event = asyncio.Event()
+
+ async def set_signal():
+ event.set()
+ hass.helpers.dispatcher.async_dispatcher_connect(
+ SIGNAL_HEOS_SOURCES_UPDATED, set_signal)
+
+ input_sources.clear()
+ player.heos.dispatcher.send(
+ const.SIGNAL_CONTROLLER_EVENT, const.EVENT_SOURCES_CHANGED)
+ await event.wait()
+ source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list
+ assert len(source_list) == 2
+ state = hass.states.get('media_player.test_player')
+ assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list
+
+
+async def test_updates_from_user_changed(
+ hass, config_entry, config, controller):
+ """Tests player updates from changes in user."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ event = asyncio.Event()
+
+ async def set_signal():
+ event.set()
+ hass.helpers.dispatcher.async_dispatcher_connect(
+ SIGNAL_HEOS_SOURCES_UPDATED, set_signal)
+
+ controller.is_signed_in = False
+ controller.signed_in_username = None
+ player.heos.dispatcher.send(
+ const.SIGNAL_CONTROLLER_EVENT, const.EVENT_USER_CHANGED)
+ await event.wait()
+ source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list
+ assert len(source_list) == 1
+ state = hass.states.get('media_player.test_player')
+ assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list
+
+
+async def test_clear_playlist(hass, config_entry, config, controller, caplog):
+ """Test the clear playlist service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_CLEAR_PLAYLIST,
+ {ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
+ assert player.clear_queue.call_count == 1
+ player.clear_queue.reset_mock()
+ player.clear_queue.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to clear playlist: Failure (1)" in caplog.text
+
+
+async def test_pause(hass, config_entry, config, controller, caplog):
+ """Test the pause service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PAUSE,
+ {ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
+ assert player.pause.call_count == 1
+ player.pause.reset_mock()
+ player.pause.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to pause: Failure (1)" in caplog.text
+
+
+async def test_play(hass, config_entry, config, controller, caplog):
+ """Test the play service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PLAY,
+ {ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
+ assert player.play.call_count == 1
+ player.play.reset_mock()
+ player.play.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to play: Failure (1)" in caplog.text
+
+
+async def test_previous_track(hass, config_entry, config, controller, caplog):
+ """Test the previous track service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
+ {ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
+ assert player.play_previous.call_count == 1
+ player.play_previous.reset_mock()
+ player.play_previous.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to move to previous track: Failure (1)" in caplog.text
+
+
+async def test_next_track(hass, config_entry, config, controller, caplog):
+ """Test the next track service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
+ {ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
+ assert player.play_next.call_count == 1
+ player.play_next.reset_mock()
+ player.play_next.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to move to next track: Failure (1)" in caplog.text
+
+
+async def test_stop(hass, config_entry, config, controller, caplog):
+ """Test the stop service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_STOP,
+ {ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
+ assert player.stop.call_count == 1
+ player.stop.reset_mock()
+ player.stop.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to stop: Failure (1)" in caplog.text
+
+
+async def test_volume_mute(hass, config_entry, config, controller, caplog):
+ """Test the volume mute service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_MUTE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_MEDIA_VOLUME_MUTED: True}, blocking=True)
+ assert player.set_mute.call_count == 1
+ player.set_mute.reset_mock()
+ player.set_mute.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to set mute: Failure (1)" in caplog.text
+
+
+async def test_shuffle_set(hass, config_entry, config, controller, caplog):
+ """Test the shuffle set service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SHUFFLE_SET,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_MEDIA_SHUFFLE: True}, blocking=True)
+ player.set_play_mode.assert_called_once_with(player.repeat, True)
+ player.set_play_mode.reset_mock()
+ player.set_play_mode.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to set shuffle: Failure (1)" in caplog.text
+
+
+async def test_volume_set(hass, config_entry, config, controller, caplog):
+ """Test the volume set service."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # First pass completes successfully, second pass raises command error
+ for _ in range(2):
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_SET,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_MEDIA_VOLUME_LEVEL: 1}, blocking=True)
+ player.set_volume.assert_called_once_with(100)
+ player.set_volume.reset_mock()
+ player.set_volume.side_effect = CommandError(None, "Failure", 1)
+ assert "Unable to set volume level: Failure (1)" in caplog.text
+
+
+async def test_select_favorite(
+ hass, config_entry, config, controller, favorites):
+ """Tests selecting a music service favorite and state."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # Test set music service preset
+ favorite = favorites[1]
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_INPUT_SOURCE: favorite.name}, blocking=True)
+ player.play_favorite.assert_called_once_with(1)
+ # Test state is matched by station name
+ player.now_playing_media.station = favorite.name
+ player.heos.dispatcher.send(
+ const.SIGNAL_PLAYER_EVENT, player.player_id,
+ const.EVENT_PLAYER_STATE_CHANGED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.attributes[ATTR_INPUT_SOURCE] == favorite.name
+
+
+async def test_select_radio_favorite(
+ hass, config_entry, config, controller, favorites):
+ """Tests selecting a radio favorite and state."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # Test set radio preset
+ favorite = favorites[2]
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_INPUT_SOURCE: favorite.name}, blocking=True)
+ player.play_favorite.assert_called_once_with(2)
+ # Test state is matched by album id
+ player.now_playing_media.station = "Classical"
+ player.now_playing_media.album_id = favorite.media_id
+ player.heos.dispatcher.send(
+ const.SIGNAL_PLAYER_EVENT, player.player_id,
+ const.EVENT_PLAYER_STATE_CHANGED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.attributes[ATTR_INPUT_SOURCE] == favorite.name
+
+
+async def test_select_radio_favorite_command_error(
+ hass, config_entry, config, controller, favorites, caplog):
+ """Tests command error loged when playing favorite."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # Test set radio preset
+ favorite = favorites[2]
+ player.play_favorite.side_effect = CommandError(None, "Failure", 1)
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_INPUT_SOURCE: favorite.name}, blocking=True)
+ player.play_favorite.assert_called_once_with(2)
+ assert "Unable to select source: Failure (1)" in caplog.text
+
+
+async def test_select_input_source(
+ hass, config_entry, config, controller, input_sources):
+ """Tests selecting input source and state."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ # Test proper service called
+ input_source = input_sources[0]
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_INPUT_SOURCE: input_source.name}, blocking=True)
+ player.play_input_source.assert_called_once_with(input_source)
+ # Test state is matched by media id
+ player.now_playing_media.source_id = const.MUSIC_SOURCE_AUX_INPUT
+ player.now_playing_media.media_id = const.INPUT_AUX_IN_1
+ player.heos.dispatcher.send(
+ const.SIGNAL_PLAYER_EVENT, player.player_id,
+ const.EVENT_PLAYER_STATE_CHANGED)
+ await hass.async_block_till_done()
+ state = hass.states.get('media_player.test_player')
+ assert state.attributes[ATTR_INPUT_SOURCE] == input_source.name
+
+
+async def test_select_input_unknown(
+ hass, config_entry, config, controller, caplog):
+ """Tests selecting an unknown input."""
+ await setup_platform(hass, config_entry, config)
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_INPUT_SOURCE: "Unknown"}, blocking=True)
+ assert "Unknown source: Unknown" in caplog.text
+
+
+async def test_select_input_command_error(
+ hass, config_entry, config, controller, caplog, input_sources):
+ """Tests selecting an unknown input."""
+ await setup_platform(hass, config_entry, config)
+ player = controller.players[1]
+ input_source = input_sources[0]
+ player.play_input_source.side_effect = CommandError(None, "Failure", 1)
+ await hass.services.async_call(
+ MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
+ {ATTR_ENTITY_ID: 'media_player.test_player',
+ ATTR_INPUT_SOURCE: input_source.name}, blocking=True)
+ player.play_input_source.assert_called_once_with(input_source)
+ assert "Unable to select source: Failure (1)" in caplog.text
+
+
+async def test_unload_config_entry(hass, config_entry, config, controller):
+ """Test the player is removed when the config entry is unloaded."""
+ await setup_platform(hass, config_entry, config)
+ await config_entry.async_unload(hass)
+ assert not hass.states.get('media_player.test_player')
diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py
index 6f3957827eb..c9798f6302a 100644
--- a/tests/components/homekit/test_accessories.py
+++ b/tests/components/homekit/test_accessories.py
@@ -13,7 +13,7 @@ from homeassistant.components.homekit.const import (
ATTR_DISPLAY_NAME, ATTR_VALUE,
BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_FIRMWARE_REVISION,
CHAR_MANUFACTURER, CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER,
- MANUFACTURER, SERV_ACCESSORY_INFO)
+ CONF_LINKED_BATTERY_SENSOR, MANUFACTURER, SERV_ACCESSORY_INFO)
from homeassistant.const import (
__version__, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID,
ATTR_SERVICE, ATTR_NOW, EVENT_TIME_CHANGED)
@@ -156,6 +156,61 @@ async def test_battery_service(hass, hk_driver, caplog):
assert acc._char_charging.value == 0
+async def test_linked_battery_sensor(hass, hk_driver, caplog):
+ """Test battery service with linked_battery_sensor."""
+ entity_id = 'homekit.accessory'
+ linked_battery = 'sensor.battery'
+ hass.states.async_set(entity_id, 'open', {ATTR_BATTERY_LEVEL: 100})
+ hass.states.async_set(linked_battery, 50, None)
+ await hass.async_block_till_done()
+
+ acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2,
+ {CONF_LINKED_BATTERY_SENSOR: linked_battery})
+ acc.update_state = lambda x: None
+ assert acc.linked_battery_sensor == linked_battery
+
+ await hass.async_add_job(acc.run)
+ await hass.async_block_till_done()
+ assert acc._char_battery.value == 50
+ assert acc._char_low_battery.value == 0
+ assert acc._char_charging.value == 2
+
+ hass.states.async_set(linked_battery, 10, None)
+ await hass.async_block_till_done()
+ assert acc._char_battery.value == 10
+ assert acc._char_low_battery.value == 1
+
+ # Ignore battery change on entity if it has linked_battery
+ hass.states.async_set(entity_id, 'open', {ATTR_BATTERY_LEVEL: 90})
+ await hass.async_block_till_done()
+ assert acc._char_battery.value == 10
+
+ # Test none numeric state for linked_battery
+ hass.states.async_set(linked_battery, 'error', None)
+ await hass.async_block_till_done()
+ assert acc._char_battery.value == 10
+ assert 'ERROR' not in caplog.text
+
+ # Test charging
+ hass.states.async_set(linked_battery, 20, {ATTR_BATTERY_CHARGING: True})
+ await hass.async_block_till_done()
+
+ acc = HomeAccessory(hass, hk_driver, 'Battery Service', entity_id, 2,
+ {CONF_LINKED_BATTERY_SENSOR: linked_battery})
+ acc.update_state = lambda x: None
+ await hass.async_add_job(acc.run)
+ await hass.async_block_till_done()
+ assert acc._char_battery.value == 20
+ assert acc._char_low_battery.value == 0
+ assert acc._char_charging.value == 1
+
+ hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False})
+ await hass.async_block_till_done()
+ assert acc._char_battery.value == 100
+ assert acc._char_low_battery.value == 0
+ assert acc._char_charging.value == 0
+
+
async def test_call_service(hass, hk_driver, events):
"""Test call_service method."""
entity_id = 'homekit.accessory'
diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py
index c86b1353c48..9ffcfe5c01e 100644
--- a/tests/components/homekit/test_util.py
+++ b/tests/components/homekit/test_util.py
@@ -3,9 +3,9 @@ import pytest
import voluptuous as vol
from homeassistant.components.homekit.const import (
- CONF_FEATURE, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
- HOMEKIT_NOTIFY_ID, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
- TYPE_SWITCH, TYPE_VALVE)
+ CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR,
+ FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, HOMEKIT_NOTIFY_ID, TYPE_FAUCET,
+ TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
from homeassistant.components.homekit.util import (
HomeKitSpeedMapping, SpeedRange, convert_to_float, density_to_air_quality,
dismiss_setup_message, show_setup_message, temperature_to_homekit,
@@ -25,6 +25,9 @@ def test_validate_entity_config():
"""Test validate entities."""
configs = [None, [], 'string', 12345,
{'invalid_entity_id': {}}, {'demo.test': 1},
+ {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR: None}},
+ {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
+ 'switch.demo'}},
{'demo.test': 'test'}, {'demo.test': [1, 2]},
{'demo.test': None}, {'demo.test': {CONF_NAME: None}},
{'media_player.test': {CONF_FEATURE_LIST: [
@@ -42,6 +45,11 @@ def test_validate_entity_config():
assert vec({'demo.test': {CONF_NAME: 'Name'}}) == \
{'demo.test': {CONF_NAME: 'Name'}}
+ assert vec({'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
+ 'sensor.demo_battery'}}) == \
+ {'binary_sensor.demo': {CONF_LINKED_BATTERY_SENSOR:
+ 'sensor.demo_battery'}}
+
assert vec({'alarm_control_panel.demo': {}}) == \
{'alarm_control_panel.demo': {ATTR_CODE: None}}
assert vec({'alarm_control_panel.demo': {ATTR_CODE: '1234'}}) == \
@@ -183,6 +191,7 @@ def test_homekit_speed_mapping():
def test_speed_to_homekit():
"""Test speed conversion from HA to Homekit."""
speed_mapping = HomeKitSpeedMapping(['off', 'low', 'high'])
+ assert speed_mapping.speed_to_homekit(None) is None
assert speed_mapping.speed_to_homekit('off') == 0
assert speed_mapping.speed_to_homekit('low') == 50
assert speed_mapping.speed_to_homekit('high') == 100
@@ -191,6 +200,7 @@ def test_speed_to_homekit():
def test_speed_to_states():
"""Test speed conversion from Homekit to HA."""
speed_mapping = HomeKitSpeedMapping(['off', 'low', 'high'])
+ assert speed_mapping.speed_to_states(-1) == 'off'
assert speed_mapping.speed_to_states(0) == 'off'
assert speed_mapping.speed_to_states(33) == 'off'
assert speed_mapping.speed_to_states(34) == 'low'
diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py
index 2d659d42dfb..5ad197f8294 100644
--- a/tests/components/homekit_controller/common.py
+++ b/tests/components/homekit_controller/common.py
@@ -1,5 +1,6 @@
"""Code to support homekit_controller tests."""
import json
+import os
from datetime import timedelta
from unittest import mock
@@ -10,10 +11,11 @@ from homekit.model import Accessory, get_id
from homekit.exceptions import AccessoryNotFoundError
from homeassistant.components.homekit_controller import SERVICE_HOMEKIT
from homeassistant.components.homekit_controller.const import (
- DOMAIN, HOMEKIT_ACCESSORY_DISPATCH)
+ CONTROLLER, DOMAIN, HOMEKIT_ACCESSORY_DISPATCH)
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
-from tests.common import async_fire_time_changed, fire_service_discovered
+from tests.common import (
+ async_fire_time_changed, fire_service_discovered, load_fixture)
class FakePairing:
@@ -149,10 +151,13 @@ class FakeService(AbstractService):
return char
-def setup_accessories_from_file(path):
+async def setup_accessories_from_file(hass, path):
"""Load an collection of accessory defs from JSON data."""
- with open(path, 'r') as accessories_data:
- accessories_json = json.load(accessories_data)
+ accessories_fixture = await hass.async_add_executor_job(
+ load_fixture,
+ os.path.join('homekit_controller', path),
+ )
+ accessories_json = json.loads(accessories_fixture)
accessories = []
@@ -222,6 +227,30 @@ async def setup_test_accessories(hass, accessories, capitalize=False):
return pairing
+async def device_config_changed(hass, accessories):
+ """Discover new devices added to HomeAssistant at runtime."""
+ # Update the accessories our FakePairing knows about
+ controller = hass.data[CONTROLLER]
+ pairing = controller.pairings['00:00:00:00:00:00']
+ pairing.accessories = accessories
+
+ discovery_info = {
+ 'host': '127.0.0.1',
+ 'port': 8080,
+ 'properties': {
+ 'md': 'TestDevice',
+ 'id': '00:00:00:00:00:00',
+ 'c#': '2',
+ 'sf': '0',
+ }
+ }
+
+ fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info)
+
+ # Wait for services to reconfigure
+ await hass.async_block_till_done()
+
+
async def setup_test_component(hass, services, capitalize=False, suffix=None):
"""Load a fake homekit accessory based on a homekit accessory model.
diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py
new file mode 100644
index 00000000000..e0738d67083
--- /dev/null
+++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py
@@ -0,0 +1,41 @@
+"""
+Regression tests for Aqara Gateway V3.
+
+https://github.com/home-assistant/home-assistant/issues/20957
+"""
+
+from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR
+from tests.components.homekit_controller.common import (
+ setup_accessories_from_file, setup_test_accessories, Helper
+)
+
+
+async def test_aqara_gateway_setup(hass):
+ """Test that a Aqara Gateway can be correctly setup in HA."""
+ accessories = await setup_accessories_from_file(
+ hass, 'aqara_gateway.json')
+ pairing = await setup_test_accessories(hass, accessories)
+
+ entity_registry = await hass.helpers.entity_registry.async_get_registry()
+
+ # Check that the light is correctly found and set up
+ alarm_id = "alarm_control_panel.aqara_hub_1563"
+ alarm = entity_registry.async_get(alarm_id)
+ assert alarm.unique_id == 'homekit-0000000123456789-66304'
+
+ alarm_helper = Helper(
+ hass, 'alarm_control_panel.aqara_hub_1563', pairing, accessories[0])
+ alarm_state = await alarm_helper.poll_and_get_state()
+ assert alarm_state.attributes['friendly_name'] == 'Aqara Hub-1563'
+
+ # Check that the light is correctly found and set up
+ light = entity_registry.async_get('light.aqara_hub_1563')
+ assert light.unique_id == 'homekit-0000000123456789-65792'
+
+ light_helper = Helper(
+ hass, 'light.aqara_hub_1563', pairing, accessories[0])
+ light_state = await light_helper.poll_and_get_state()
+ assert light_state.attributes['friendly_name'] == 'Aqara Hub-1563'
+ assert light_state.attributes['supported_features'] == (
+ SUPPORT_BRIGHTNESS | SUPPORT_COLOR
+ )
diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py
new file mode 100644
index 00000000000..0831cd5b780
--- /dev/null
+++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py
@@ -0,0 +1,83 @@
+"""
+Regression tests for Ecobee 3.
+
+https://github.com/home-assistant/home-assistant/issues/15336
+"""
+
+from homeassistant.components.climate.const import (
+ SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY,
+ SUPPORT_OPERATION_MODE)
+from tests.components.homekit_controller.common import (
+ device_config_changed, setup_accessories_from_file, setup_test_accessories,
+ Helper
+)
+
+
+async def test_ecobee3_setup(hass):
+ """Test that a Ecbobee 3 can be correctly setup in HA."""
+ accessories = await setup_accessories_from_file(hass, 'ecobee3.json')
+ pairing = await setup_test_accessories(hass, accessories)
+
+ entity_registry = await hass.helpers.entity_registry.async_get_registry()
+
+ climate = entity_registry.async_get('climate.homew')
+ assert climate.unique_id == 'homekit-123456789012-16'
+
+ climate_helper = Helper(hass, 'climate.homew', pairing, accessories[0])
+ climate_state = await climate_helper.poll_and_get_state()
+ assert climate_state.attributes['friendly_name'] == 'HomeW'
+ assert climate_state.attributes['supported_features'] == (
+ SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY |
+ SUPPORT_OPERATION_MODE
+ )
+
+ occ1 = entity_registry.async_get('binary_sensor.kitchen')
+ assert occ1.unique_id == 'homekit-AB1C-56'
+
+ occ1_helper = Helper(
+ hass, 'binary_sensor.kitchen', pairing, accessories[0])
+ occ1_state = await occ1_helper.poll_and_get_state()
+ assert occ1_state.attributes['friendly_name'] == 'Kitchen'
+
+ occ2 = entity_registry.async_get('binary_sensor.porch')
+ assert occ2.unique_id == 'homekit-AB2C-56'
+
+ occ3 = entity_registry.async_get('binary_sensor.basement')
+ assert occ3.unique_id == 'homekit-AB3C-56'
+
+
+async def test_ecobee3_add_sensors_at_runtime(hass):
+ """Test that new sensors are automatically added."""
+ entity_registry = await hass.helpers.entity_registry.async_get_registry()
+
+ # Set up a base Ecobee 3 with no additional sensors.
+ # There shouldn't be any entities but climate visible.
+ accessories = await setup_accessories_from_file(
+ hass, 'ecobee3_no_sensors.json')
+ await setup_test_accessories(hass, accessories)
+
+ climate = entity_registry.async_get('climate.homew')
+ assert climate.unique_id == 'homekit-123456789012-16'
+
+ occ1 = entity_registry.async_get('binary_sensor.kitchen')
+ assert occ1 is None
+
+ occ2 = entity_registry.async_get('binary_sensor.porch')
+ assert occ2 is None
+
+ occ3 = entity_registry.async_get('binary_sensor.basement')
+ assert occ3 is None
+
+ # Now added 3 new sensors at runtime - sensors should appear and climate
+ # shouldn't be duplicated.
+ accessories = await setup_accessories_from_file(hass, 'ecobee3.json')
+ await device_config_changed(hass, accessories)
+
+ occ1 = entity_registry.async_get('binary_sensor.kitchen')
+ assert occ1.unique_id == 'homekit-AB1C-56'
+
+ occ2 = entity_registry.async_get('binary_sensor.porch')
+ assert occ2.unique_id == 'homekit-AB2C-56'
+
+ occ3 = entity_registry.async_get('binary_sensor.basement')
+ assert occ3.unique_id == 'homekit-AB3C-56'
diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py
index 7b7981cf6de..a741885c6d5 100644
--- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py
+++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py
@@ -1,6 +1,5 @@
"""Make sure that existing Koogeek LS1 support isn't broken."""
-import os
from datetime import timedelta
from unittest import mock
@@ -19,8 +18,7 @@ LIGHT_ON = ('lightbulb', 'on')
async def test_koogeek_ls1_setup(hass):
"""Test that a Koogeek LS1 can be correctly setup in HA."""
- profile_path = os.path.join(os.path.dirname(__file__), 'koogeek_ls1.json')
- accessories = setup_accessories_from_file(profile_path)
+ accessories = await setup_accessories_from_file(hass, 'koogeek_ls1.json')
pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
@@ -50,8 +48,7 @@ async def test_recover_from_failure(hass, utcnow, failure_cls):
See https://github.com/home-assistant/home-assistant/issues/18949
"""
- profile_path = os.path.join(os.path.dirname(__file__), 'koogeek_ls1.json')
- accessories = setup_accessories_from_file(profile_path)
+ accessories = await setup_accessories_from_file(hass, 'koogeek_ls1.json')
pairing = await setup_test_accessories(hass, accessories)
helper = Helper(hass, 'light.koogeek_ls1_20833f', pairing, accessories[0])
diff --git a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py
new file mode 100644
index 00000000000..1869161b1f8
--- /dev/null
+++ b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py
@@ -0,0 +1,29 @@
+"""
+Regression tests for Aqara Gateway V3.
+
+https://github.com/home-assistant/home-assistant/issues/20885
+"""
+
+from homeassistant.components.climate.const import (
+ SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
+from tests.components.homekit_controller.common import (
+ setup_accessories_from_file, setup_test_accessories, Helper
+)
+
+
+async def test_lennox_e30_setup(hass):
+ """Test that a Lennox E30 can be correctly setup in HA."""
+ accessories = await setup_accessories_from_file(hass, 'lennox_e30.json')
+ pairing = await setup_test_accessories(hass, accessories)
+
+ entity_registry = await hass.helpers.entity_registry.async_get_registry()
+
+ climate = entity_registry.async_get('climate.lennox')
+ assert climate.unique_id == 'homekit-XXXXXXXX-100'
+
+ climate_helper = Helper(hass, 'climate.lennox', pairing, accessories[0])
+ climate_state = await climate_helper.poll_and_get_state()
+ assert climate_state.attributes['friendly_name'] == 'Lennox'
+ assert climate_state.attributes['supported_features'] == (
+ SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
+ )
diff --git a/tests/components/homekit_controller/test_climate.py b/tests/components/homekit_controller/test_climate.py
index b04a57fa967..4c0a5debb65 100644
--- a/tests/components/homekit_controller/test_climate.py
+++ b/tests/components/homekit_controller/test_climate.py
@@ -1,14 +1,69 @@
"""Basic checks for HomeKitclimate."""
from homeassistant.components.climate.const import (
- DOMAIN, SERVICE_SET_OPERATION_MODE, SERVICE_SET_TEMPERATURE)
+ DOMAIN, SERVICE_SET_OPERATION_MODE, SERVICE_SET_TEMPERATURE,
+ SERVICE_SET_HUMIDITY)
from tests.components.homekit_controller.common import (
- setup_test_component)
+ FakeService, setup_test_component)
HEATING_COOLING_TARGET = ('thermostat', 'heating-cooling.target')
HEATING_COOLING_CURRENT = ('thermostat', 'heating-cooling.current')
TEMPERATURE_TARGET = ('thermostat', 'temperature.target')
TEMPERATURE_CURRENT = ('thermostat', 'temperature.current')
+HUMIDITY_TARGET = ('thermostat', 'relative-humidity.target')
+HUMIDITY_CURRENT = ('thermostat', 'relative-humidity.current')
+
+
+def create_thermostat_service():
+ """Define thermostat characteristics."""
+ service = FakeService('public.hap.service.thermostat')
+
+ char = service.add_characteristic('heating-cooling.target')
+ char.value = 0
+
+ char = service.add_characteristic('heating-cooling.current')
+ char.value = 0
+
+ char = service.add_characteristic('temperature.target')
+ char.value = 0
+
+ char = service.add_characteristic('temperature.current')
+ char.value = 0
+
+ char = service.add_characteristic('relative-humidity.target')
+ char.value = 0
+
+ char = service.add_characteristic('relative-humidity.current')
+ char.value = 0
+
+ return service
+
+
+async def test_climate_respect_supported_op_modes_1(hass, utcnow):
+ """Test that climate respects minValue/maxValue hints."""
+ service = FakeService('public.hap.service.thermostat')
+ char = service.add_characteristic('heating-cooling.target')
+ char.value = 0
+ char.minValue = 0
+ char.maxValue = 1
+
+ helper = await setup_test_component(hass, [service])
+
+ state = await helper.poll_and_get_state()
+ assert state.attributes['operation_list'] == ['off', 'heat']
+
+
+async def test_climate_respect_supported_op_modes_2(hass, utcnow):
+ """Test that climate respects validValue hints."""
+ service = FakeService('public.hap.service.thermostat')
+ char = service.add_characteristic('heating-cooling.target')
+ char.value = 0
+ char.validValues = [0, 1, 2]
+
+ helper = await setup_test_component(hass, [service])
+
+ state = await helper.poll_and_get_state()
+ assert state.attributes['operation_list'] == ['off', 'heat', 'cool']
async def test_climate_change_thermostat_state(hass, utcnow):
@@ -50,28 +105,49 @@ async def test_climate_change_thermostat_temperature(hass, utcnow):
assert helper.characteristics[TEMPERATURE_TARGET].value == 25
+async def test_climate_change_thermostat_humidity(hass, utcnow):
+ """Test that we can turn a HomeKit thermostat on and off again."""
+ helper = await setup_test_component(hass, [create_thermostat_service()])
+
+ await hass.services.async_call(DOMAIN, SERVICE_SET_HUMIDITY, {
+ 'entity_id': 'climate.testdevice',
+ 'humidity': 50,
+ }, blocking=True)
+ assert helper.characteristics[HUMIDITY_TARGET].value == 50
+
+ await hass.services.async_call(DOMAIN, SERVICE_SET_HUMIDITY, {
+ 'entity_id': 'climate.testdevice',
+ 'humidity': 45,
+ }, blocking=True)
+ assert helper.characteristics[HUMIDITY_TARGET].value == 45
+
+
async def test_climate_read_thermostat_state(hass, utcnow):
"""Test that we can read the state of a HomeKit thermostat accessory."""
- from homekit.model.services import ThermostatService
-
- helper = await setup_test_component(hass, [ThermostatService()])
+ helper = await setup_test_component(hass, [create_thermostat_service()])
# Simulate that heating is on
helper.characteristics[TEMPERATURE_CURRENT].value = 19
helper.characteristics[TEMPERATURE_TARGET].value = 21
helper.characteristics[HEATING_COOLING_CURRENT].value = 1
helper.characteristics[HEATING_COOLING_TARGET].value = 1
+ helper.characteristics[HUMIDITY_CURRENT].value = 50
+ helper.characteristics[HUMIDITY_TARGET].value = 45
state = await helper.poll_and_get_state()
assert state.state == 'heat'
assert state.attributes['current_temperature'] == 19
+ assert state.attributes['current_humidity'] == 50
# Simulate that cooling is on
helper.characteristics[TEMPERATURE_CURRENT].value = 21
helper.characteristics[TEMPERATURE_TARGET].value = 19
helper.characteristics[HEATING_COOLING_CURRENT].value = 2
helper.characteristics[HEATING_COOLING_TARGET].value = 2
+ helper.characteristics[HUMIDITY_CURRENT].value = 45
+ helper.characteristics[HUMIDITY_TARGET].value = 45
state = await helper.poll_and_get_state()
assert state.state == 'cool'
assert state.attributes['current_temperature'] == 21
+ assert state.attributes['current_humidity'] == 45
diff --git a/tests/components/homekit_controller/test_cover.py b/tests/components/homekit_controller/test_cover.py
index 62fce4325c7..19ccc21b7e8 100644
--- a/tests/components/homekit_controller/test_cover.py
+++ b/tests/components/homekit_controller/test_cover.py
@@ -5,6 +5,7 @@ from tests.components.homekit_controller.common import (
POSITION_STATE = ('window-covering', 'position.state')
POSITION_CURRENT = ('window-covering', 'position.current')
POSITION_TARGET = ('window-covering', 'position.target')
+POSITION_HOLD = ('window-covering', 'position.hold')
H_TILT_CURRENT = ('window-covering', 'horizontal-tilt.current')
H_TILT_TARGET = ('window-covering', 'horizontal-tilt.target')
@@ -166,6 +167,17 @@ async def test_write_window_cover_tilt_vertical(hass, utcnow):
assert helper.characteristics[V_TILT_TARGET].value == 90
+async def test_window_cover_stop(hass, utcnow):
+ """Test that vertical tilt is written correctly."""
+ window_cover = create_window_covering_service_with_v_tilt()
+ helper = await setup_test_component(hass, [window_cover])
+
+ await hass.services.async_call('cover', 'stop_cover', {
+ 'entity_id': helper.entity_id,
+ }, blocking=True)
+ assert helper.characteristics[POSITION_HOLD].value == 1
+
+
def create_garage_door_opener_service():
"""Define a garage-door-opener chars as per page 217 of HAP spec."""
service = FakeService('public.hap.service.garage-door-opener')
diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py
index e8e0c0a2929..2674dac6b1e 100644
--- a/tests/components/honeywell/test_climate.py
+++ b/tests/components/honeywell/test_climate.py
@@ -1,9 +1,9 @@
"""The test the Honeywell thermostat module."""
-import socket
import unittest
from unittest import mock
import voluptuous as vol
+import requests.exceptions
import somecomfort
from homeassistant.const import (
@@ -247,7 +247,8 @@ class TestHoneywell(unittest.TestCase):
honeywell.CONF_AWAY_TEMPERATURE: 20,
honeywell.CONF_REGION: 'eu',
}
- mock_evo.return_value.temperatures.side_effect = socket.error
+ mock_evo.return_value.temperatures.side_effect = \
+ requests.exceptions.RequestException
add_entities = mock.MagicMock()
hass = mock.MagicMock()
assert not honeywell.setup_platform(hass, config, add_entities)
diff --git a/tests/components/html5/test_notify.py b/tests/components/html5/test_notify.py
index 140544bf9ea..cae4db6434a 100644
--- a/tests/components/html5/test_notify.py
+++ b/tests/components/html5/test_notify.py
@@ -61,6 +61,7 @@ async def mock_client(hass, hass_client, registrations=None):
'platform': 'html5'
}
})
+ await hass.async_block_till_done()
return await hass_client()
diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py
index 855a12e2620..5b383afc53d 100644
--- a/tests/components/hue/test_bridge.py
+++ b/tests/components/hue/test_bridge.py
@@ -21,9 +21,13 @@ async def test_bridge_setup():
assert await hue_bridge.async_setup() is True
assert hue_bridge.api is api
- assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 1
- assert hass.config_entries.async_forward_entry_setup.mock_calls[0][1] == \
- (entry, 'light')
+ forward_entries = set(
+ c[1][1]
+ for c in
+ hass.config_entries.async_forward_entry_setup.mock_calls
+ )
+ assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3
+ assert forward_entries == set(['light', 'binary_sensor', 'sensor'])
async def test_bridge_setup_invalid_username():
@@ -84,11 +88,11 @@ async def test_reset_unloads_entry_if_setup():
assert await hue_bridge.async_setup() is True
assert len(hass.services.async_register.mock_calls) == 1
- assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 1
+ assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3
hass.config_entries.async_forward_entry_unload.return_value = \
mock_coro(True)
assert await hue_bridge.async_reset()
- assert len(hass.config_entries.async_forward_entry_unload.mock_calls) == 1
+ assert len(hass.config_entries.async_forward_entry_unload.mock_calls) == 3
assert len(hass.services.async_remove.mock_calls) == 1
diff --git a/tests/components/hue/test_sensor_base.py b/tests/components/hue/test_sensor_base.py
new file mode 100644
index 00000000000..6259921dcfb
--- /dev/null
+++ b/tests/components/hue/test_sensor_base.py
@@ -0,0 +1,485 @@
+"""Philips Hue sensors platform tests."""
+import asyncio
+from collections import deque
+import datetime
+import logging
+from unittest.mock import Mock
+
+import aiohue
+from aiohue.sensors import Sensors
+import pytest
+
+from homeassistant import config_entries
+from homeassistant.components import hue
+from homeassistant.components.hue import sensor_base as hue_sensor_base
+
+_LOGGER = logging.getLogger(__name__)
+
+PRESENCE_SENSOR_1_PRESENT = {
+ "state": {
+ "presence": True,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T00:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "ledindication": False,
+ "usertest": False,
+ "sensitivity": 2,
+ "sensitivitymax": 2,
+ "pending": []
+ },
+ "name": "Living room sensor",
+ "type": "ZLLPresence",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue motion sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:77-02-0406",
+ "capabilities": {
+ "certified": True
+ }
+}
+LIGHT_LEVEL_SENSOR_1 = {
+ "state": {
+ "lightlevel": 1,
+ "dark": True,
+ "daylight": True,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T00:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "tholddark": 12467,
+ "tholdoffset": 7000,
+ "ledindication": False,
+ "usertest": False,
+ "pending": []
+ },
+ "name": "Hue ambient light sensor 1",
+ "type": "ZLLLightLevel",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue ambient light sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:77-02-0400",
+ "capabilities": {
+ "certified": True
+ }
+}
+TEMPERATURE_SENSOR_1 = {
+ "state": {
+ "temperature": 1775,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T01:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "ledindication": False,
+ "usertest": False,
+ "pending": []
+ },
+ "name": "Hue temperature sensor 1",
+ "type": "ZLLTemperature",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue temperature sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:77-02-0402",
+ "capabilities": {
+ "certified": True
+ }
+}
+PRESENCE_SENSOR_2_NOT_PRESENT = {
+ "state": {
+ "presence": False,
+ "lastupdated": "2019-01-01T00:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T01:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "ledindication": False,
+ "usertest": False,
+ "sensitivity": 2,
+ "sensitivitymax": 2,
+ "pending": []
+ },
+ "name": "Kitchen sensor",
+ "type": "ZLLPresence",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue motion sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:88-02-0406",
+ "capabilities": {
+ "certified": True
+ }
+}
+LIGHT_LEVEL_SENSOR_2 = {
+ "state": {
+ "lightlevel": 10001,
+ "dark": True,
+ "daylight": True,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T00:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "tholddark": 12467,
+ "tholdoffset": 7000,
+ "ledindication": False,
+ "usertest": False,
+ "pending": []
+ },
+ "name": "Hue ambient light sensor 2",
+ "type": "ZLLLightLevel",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue ambient light sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:88-02-0400",
+ "capabilities": {
+ "certified": True
+ }
+}
+TEMPERATURE_SENSOR_2 = {
+ "state": {
+ "temperature": 1875,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T01:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "ledindication": False,
+ "usertest": False,
+ "pending": []
+ },
+ "name": "Hue temperature sensor 2",
+ "type": "ZLLTemperature",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue temperature sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:88-02-0402",
+ "capabilities": {
+ "certified": True
+ }
+}
+PRESENCE_SENSOR_3_PRESENT = {
+ "state": {
+ "presence": True,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T00:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "ledindication": False,
+ "usertest": False,
+ "sensitivity": 2,
+ "sensitivitymax": 2,
+ "pending": []
+ },
+ "name": "Bedroom sensor",
+ "type": "ZLLPresence",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue motion sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:99-02-0406",
+ "capabilities": {
+ "certified": True
+ }
+}
+LIGHT_LEVEL_SENSOR_3 = {
+ "state": {
+ "lightlevel": 1,
+ "dark": True,
+ "daylight": True,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T00:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "tholddark": 12467,
+ "tholdoffset": 7000,
+ "ledindication": False,
+ "usertest": False,
+ "pending": []
+ },
+ "name": "Hue ambient light sensor 3",
+ "type": "ZLLLightLevel",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue ambient light sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:99-02-0400",
+ "capabilities": {
+ "certified": True
+ }
+}
+TEMPERATURE_SENSOR_3 = {
+ "state": {
+ "temperature": 1775,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "swupdate": {
+ "state": "noupdates",
+ "lastinstall": "2019-01-01T01:00:00"
+ },
+ "config": {
+ "on": True,
+ "battery": 100,
+ "reachable": True,
+ "alert": "none",
+ "ledindication": False,
+ "usertest": False,
+ "pending": []
+ },
+ "name": "Hue temperature sensor 3",
+ "type": "ZLLTemperature",
+ "modelid": "SML001",
+ "manufacturername": "Philips",
+ "productname": "Hue temperature sensor",
+ "swversion": "6.1.1.27575",
+ "uniqueid": "00:11:22:33:44:55:66:99-02-0402",
+ "capabilities": {
+ "certified": True
+ }
+}
+UNSUPPORTED_SENSOR = {
+ "state": {
+ "status": 0,
+ "lastupdated": "2019-01-01T01:00:00"
+ },
+ "config": {
+ "on": True,
+ "reachable": True
+ },
+ "name": "Unsupported sensor",
+ "type": "CLIPGenericStatus",
+ "modelid": "PHWA01",
+ "manufacturername": "Philips",
+ "swversion": "1.0",
+ "uniqueid": "arbitrary",
+ "recycle": True
+}
+SENSOR_RESPONSE = {
+ "1": PRESENCE_SENSOR_1_PRESENT,
+ "2": LIGHT_LEVEL_SENSOR_1,
+ "3": TEMPERATURE_SENSOR_1,
+ "4": PRESENCE_SENSOR_2_NOT_PRESENT,
+ "5": LIGHT_LEVEL_SENSOR_2,
+ "6": TEMPERATURE_SENSOR_2,
+}
+
+
+@pytest.fixture
+def mock_bridge(hass):
+ """Mock a Hue bridge."""
+ bridge = Mock(
+ available=True,
+ allow_unreachable=False,
+ allow_groups=False,
+ api=Mock(),
+ spec=hue.HueBridge
+ )
+ bridge.mock_requests = []
+ # We're using a deque so we can schedule multiple responses
+ # and also means that `popleft()` will blow up if we get more updates
+ # than expected.
+ bridge.mock_sensor_responses = deque()
+
+ async def mock_request(method, path, **kwargs):
+ kwargs['method'] = method
+ kwargs['path'] = path
+ bridge.mock_requests.append(kwargs)
+
+ if path == 'sensors':
+ return bridge.mock_sensor_responses.popleft()
+ return None
+
+ bridge.api.config.apiversion = '9.9.9'
+ bridge.api.sensors = Sensors({}, mock_request)
+
+ return bridge
+
+
+@pytest.fixture
+def increase_scan_interval(hass):
+ """Increase the SCAN_INTERVAL to prevent unexpected scans during tests."""
+ hue_sensor_base.SensorManager.SCAN_INTERVAL = datetime.timedelta(days=365)
+
+
+async def setup_bridge(hass, mock_bridge):
+ """Load the Hue platform with the provided bridge."""
+ hass.config.components.add(hue.DOMAIN)
+ hass.data[hue.DOMAIN] = {'mock-host': mock_bridge}
+ config_entry = config_entries.ConfigEntry(1, hue.DOMAIN, 'Mock Title', {
+ 'host': 'mock-host'
+ }, 'test', config_entries.CONN_CLASS_LOCAL_POLL)
+ await hass.config_entries.async_forward_entry_setup(
+ config_entry, 'binary_sensor')
+ await hass.config_entries.async_forward_entry_setup(
+ config_entry, 'sensor')
+ # and make sure it completes before going further
+ await hass.async_block_till_done()
+
+
+async def test_no_sensors(hass, mock_bridge):
+ """Test the update_items function when no sensors are found."""
+ mock_bridge.allow_groups = True
+ mock_bridge.mock_sensor_responses.append({})
+ await setup_bridge(hass, mock_bridge)
+ assert len(mock_bridge.mock_requests) == 1
+ assert len(hass.states.async_all()) == 0
+
+
+async def test_sensors(hass, mock_bridge):
+ """Test the update_items function with some sensors."""
+ mock_bridge.mock_sensor_responses.append(SENSOR_RESPONSE)
+ await setup_bridge(hass, mock_bridge)
+ assert len(mock_bridge.mock_requests) == 1
+ # 2 "physical" sensors with 3 virtual sensors each
+ assert len(hass.states.async_all()) == 6
+
+ presence_sensor_1 = hass.states.get(
+ 'binary_sensor.living_room_sensor_motion')
+ light_level_sensor_1 = hass.states.get(
+ 'sensor.living_room_sensor_light_level')
+ temperature_sensor_1 = hass.states.get(
+ 'sensor.living_room_sensor_temperature')
+ assert presence_sensor_1 is not None
+ assert presence_sensor_1.state == 'on'
+ assert light_level_sensor_1 is not None
+ assert light_level_sensor_1.state == '1.0'
+ assert light_level_sensor_1.name == 'Living room sensor light level'
+ assert temperature_sensor_1 is not None
+ assert temperature_sensor_1.state == '17.75'
+ assert temperature_sensor_1.name == 'Living room sensor temperature'
+
+ presence_sensor_2 = hass.states.get(
+ 'binary_sensor.kitchen_sensor_motion')
+ light_level_sensor_2 = hass.states.get(
+ 'sensor.kitchen_sensor_light_level')
+ temperature_sensor_2 = hass.states.get(
+ 'sensor.kitchen_sensor_temperature')
+ assert presence_sensor_2 is not None
+ assert presence_sensor_2.state == 'off'
+ assert light_level_sensor_2 is not None
+ assert light_level_sensor_2.state == '10.0'
+ assert light_level_sensor_2.name == 'Kitchen sensor light level'
+ assert temperature_sensor_2 is not None
+ assert temperature_sensor_2.state == '18.75'
+ assert temperature_sensor_2.name == 'Kitchen sensor temperature'
+
+
+async def test_unsupported_sensors(hass, mock_bridge):
+ """Test that unsupported sensors don't get added and don't fail."""
+ response_with_unsupported = dict(SENSOR_RESPONSE)
+ response_with_unsupported['7'] = UNSUPPORTED_SENSOR
+ mock_bridge.mock_sensor_responses.append(response_with_unsupported)
+ await setup_bridge(hass, mock_bridge)
+ assert len(mock_bridge.mock_requests) == 1
+ # 2 "physical" sensors with 3 virtual sensors each
+ assert len(hass.states.async_all()) == 6
+
+
+async def test_new_sensor_discovered(hass, mock_bridge):
+ """Test if 2nd update has a new sensor."""
+ mock_bridge.mock_sensor_responses.append(SENSOR_RESPONSE)
+
+ await setup_bridge(hass, mock_bridge)
+ assert len(mock_bridge.mock_requests) == 1
+ assert len(hass.states.async_all()) == 6
+
+ new_sensor_response = dict(SENSOR_RESPONSE)
+ new_sensor_response.update({
+ "7": PRESENCE_SENSOR_3_PRESENT,
+ "8": LIGHT_LEVEL_SENSOR_3,
+ "9": TEMPERATURE_SENSOR_3,
+ })
+
+ mock_bridge.mock_sensor_responses.append(new_sensor_response)
+
+ # Force updates to run again
+ sm = hass.data[hue.DOMAIN][hue_sensor_base.SENSOR_MANAGER]
+ await sm.async_update_items()
+
+ # To flush out the service call to update the group
+ await hass.async_block_till_done()
+
+ assert len(mock_bridge.mock_requests) == 2
+ assert len(hass.states.async_all()) == 9
+
+ presence = hass.states.get('binary_sensor.bedroom_sensor_motion')
+ assert presence is not None
+ assert presence.state == 'on'
+ temperature = hass.states.get('sensor.bedroom_sensor_temperature')
+ assert temperature is not None
+ assert temperature.state == '17.75'
+
+
+async def test_update_timeout(hass, mock_bridge):
+ """Test bridge marked as not available if timeout error during update."""
+ mock_bridge.api.sensors.update = Mock(side_effect=asyncio.TimeoutError)
+ await setup_bridge(hass, mock_bridge)
+ assert len(mock_bridge.mock_requests) == 0
+ assert len(hass.states.async_all()) == 0
+ assert mock_bridge.available is False
+
+
+async def test_update_unauthorized(hass, mock_bridge):
+ """Test bridge marked as not available if unauthorized during update."""
+ mock_bridge.api.sensors.update = Mock(side_effect=aiohue.Unauthorized)
+ await setup_bridge(hass, mock_bridge)
+ assert len(mock_bridge.mock_requests) == 0
+ assert len(hass.states.async_all()) == 0
+ assert mock_bridge.available is False
diff --git a/tests/components/ign_sismologia/__init__.py b/tests/components/ign_sismologia/__init__.py
new file mode 100644
index 00000000000..785f72013bd
--- /dev/null
+++ b/tests/components/ign_sismologia/__init__.py
@@ -0,0 +1 @@
+"""Tests for the ign_sismologia component."""
diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py
new file mode 100644
index 00000000000..3adddf3eea5
--- /dev/null
+++ b/tests/components/ign_sismologia/test_geo_location.py
@@ -0,0 +1,191 @@
+"""The tests for the IGN Sismologia (Earthquakes) Feed platform."""
+import datetime
+from unittest.mock import patch, MagicMock, call
+
+from homeassistant.components import geo_location
+from homeassistant.components.geo_location import ATTR_SOURCE
+from homeassistant.components.ign_sismologia.geo_location import (
+ ATTR_EXTERNAL_ID, SCAN_INTERVAL, ATTR_REGION,
+ ATTR_MAGNITUDE, ATTR_IMAGE_URL, ATTR_PUBLICATION_DATE, ATTR_TITLE)
+from homeassistant.const import EVENT_HOMEASSISTANT_START, \
+ CONF_RADIUS, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_FRIENDLY_NAME, \
+ ATTR_UNIT_OF_MEASUREMENT, ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE
+from homeassistant.setup import async_setup_component
+from tests.common import assert_setup_component, async_fire_time_changed
+import homeassistant.util.dt as dt_util
+
+CONFIG = {
+ geo_location.DOMAIN: [
+ {
+ 'platform': 'ign_sismologia',
+ CONF_RADIUS: 200
+ }
+ ]
+}
+
+CONFIG_WITH_CUSTOM_LOCATION = {
+ geo_location.DOMAIN: [
+ {
+ 'platform': 'ign_sismologia',
+ CONF_RADIUS: 200,
+ CONF_LATITUDE: 40.4,
+ CONF_LONGITUDE: -3.7
+ }
+ ]
+}
+
+
+def _generate_mock_feed_entry(external_id, title, distance_to_home,
+ coordinates, region=None,
+ attribution=None, published=None,
+ magnitude=None, image_url=None):
+ """Construct a mock feed entry for testing purposes."""
+ feed_entry = MagicMock()
+ feed_entry.external_id = external_id
+ feed_entry.title = title
+ feed_entry.distance_to_home = distance_to_home
+ feed_entry.coordinates = coordinates
+ feed_entry.region = region
+ feed_entry.attribution = attribution
+ feed_entry.published = published
+ feed_entry.magnitude = magnitude
+ feed_entry.image_url = image_url
+ return feed_entry
+
+
+async def test_setup(hass):
+ """Test the general setup of the platform."""
+ # Set up some mock feed entries for this test.
+ mock_entry_1 = _generate_mock_feed_entry(
+ '1234', 'Title 1', 15.5, (38.0, -3.0),
+ region='Region 1', attribution='Attribution 1',
+ published=datetime.datetime(2018, 9, 22, 8, 0,
+ tzinfo=datetime.timezone.utc),
+ magnitude=5.7, image_url='http://image.url/map.jpg')
+ mock_entry_2 = _generate_mock_feed_entry(
+ '2345', 'Title 2', 20.5, (38.1, -3.1), magnitude=4.6)
+ mock_entry_3 = _generate_mock_feed_entry(
+ '3456', 'Title 3', 25.5, (38.2, -3.2), region='Region 3')
+ mock_entry_4 = _generate_mock_feed_entry(
+ '4567', 'Title 4', 12.5, (38.3, -3.3))
+
+ # Patching 'utcnow' to gain more control over the timed update.
+ utcnow = dt_util.utcnow()
+ with patch('homeassistant.util.dt.utcnow', return_value=utcnow), \
+ patch('georss_ign_sismologia_client.'
+ 'IgnSismologiaFeed') as mock_feed:
+ mock_feed.return_value.update.return_value = 'OK', [mock_entry_1,
+ mock_entry_2,
+ mock_entry_3]
+ with assert_setup_component(1, geo_location.DOMAIN):
+ assert await async_setup_component(
+ hass, geo_location.DOMAIN, CONFIG)
+ # Artificially trigger update.
+ hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
+ # Collect events.
+ await hass.async_block_till_done()
+
+ all_states = hass.states.async_all()
+ assert len(all_states) == 3
+
+ state = hass.states.get("geo_location.m_5_7_region_1")
+ assert state is not None
+ assert state.name == "M 5.7 - Region 1"
+ assert state.attributes == {
+ ATTR_EXTERNAL_ID: "1234",
+ ATTR_LATITUDE: 38.0,
+ ATTR_LONGITUDE: -3.0,
+ ATTR_FRIENDLY_NAME: "M 5.7 - Region 1",
+ ATTR_TITLE: "Title 1",
+ ATTR_REGION: "Region 1",
+ ATTR_ATTRIBUTION: "Attribution 1",
+ ATTR_PUBLICATION_DATE:
+ datetime.datetime(
+ 2018, 9, 22, 8, 0, tzinfo=datetime.timezone.utc),
+ ATTR_IMAGE_URL: 'http://image.url/map.jpg',
+ ATTR_MAGNITUDE: 5.7,
+ ATTR_UNIT_OF_MEASUREMENT: "km",
+ ATTR_SOURCE: 'ign_sismologia'}
+ assert float(state.state) == 15.5
+
+ state = hass.states.get("geo_location.m_4_6")
+ assert state is not None
+ assert state.name == "M 4.6"
+ assert state.attributes == {
+ ATTR_EXTERNAL_ID: "2345",
+ ATTR_LATITUDE: 38.1,
+ ATTR_LONGITUDE: -3.1,
+ ATTR_FRIENDLY_NAME: "M 4.6",
+ ATTR_TITLE: "Title 2",
+ ATTR_MAGNITUDE: 4.6,
+ ATTR_UNIT_OF_MEASUREMENT: "km",
+ ATTR_SOURCE: 'ign_sismologia'}
+ assert float(state.state) == 20.5
+
+ state = hass.states.get("geo_location.region_3")
+ assert state is not None
+ assert state.name == "Region 3"
+ assert state.attributes == {
+ ATTR_EXTERNAL_ID: "3456",
+ ATTR_LATITUDE: 38.2,
+ ATTR_LONGITUDE: -3.2,
+ ATTR_FRIENDLY_NAME: "Region 3",
+ ATTR_TITLE: "Title 3",
+ ATTR_REGION: "Region 3",
+ ATTR_UNIT_OF_MEASUREMENT: "km",
+ ATTR_SOURCE: 'ign_sismologia'}
+ assert float(state.state) == 25.5
+
+ # Simulate an update - one existing, one new entry,
+ # one outdated entry
+ mock_feed.return_value.update.return_value = 'OK', [
+ mock_entry_1, mock_entry_4, mock_entry_3]
+ async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ all_states = hass.states.async_all()
+ assert len(all_states) == 3
+
+ # Simulate an update - empty data, but successful update,
+ # so no changes to entities.
+ mock_feed.return_value.update.return_value = 'OK_NO_DATA', None
+ async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ all_states = hass.states.async_all()
+ assert len(all_states) == 3
+
+ # Simulate an update - empty data, removes all entities
+ mock_feed.return_value.update.return_value = 'ERROR', None
+ async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ all_states = hass.states.async_all()
+ assert len(all_states) == 0
+
+
+async def test_setup_with_custom_location(hass):
+ """Test the setup with a custom location."""
+ # Set up some mock feed entries for this test.
+ mock_entry_1 = _generate_mock_feed_entry(
+ '1234', 'Title 1', 20.5, (38.1, -3.1))
+
+ with patch('georss_ign_sismologia_client.'
+ 'IgnSismologiaFeed') as mock_feed:
+ mock_feed.return_value.update.return_value = 'OK', [mock_entry_1]
+
+ with assert_setup_component(1, geo_location.DOMAIN):
+ assert await async_setup_component(
+ hass, geo_location.DOMAIN, CONFIG_WITH_CUSTOM_LOCATION)
+
+ # Artificially trigger update.
+ hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
+ # Collect events.
+ await hass.async_block_till_done()
+
+ all_states = hass.states.async_all()
+ assert len(all_states) == 1
+
+ assert mock_feed.call_args == call(
+ (40.4, -3.7), filter_minimum_magnitude=0.0,
+ filter_radius=200.0)
diff --git a/tests/components/introduction/__init__.py b/tests/components/introduction/__init__.py
deleted file mode 100644
index 99cea29581c..00000000000
--- a/tests/components/introduction/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Tests for the introduction component."""
diff --git a/tests/components/introduction/test_init.py b/tests/components/introduction/test_init.py
deleted file mode 100644
index c414ab97ae1..00000000000
--- a/tests/components/introduction/test_init.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""The tests for the Introduction component."""
-import unittest
-
-from homeassistant.setup import setup_component
-from homeassistant.components import introduction
-
-from tests.common import get_test_home_assistant
-
-
-class TestIntroduction(unittest.TestCase):
- """Test Introduction."""
-
- def setUp(self):
- """Set up things to be run when tests are started."""
- self.hass = get_test_home_assistant()
-
- def tearDown(self):
- """Stop down everything that was started."""
- self.hass.stop()
-
- def test_setup(self):
- """Test introduction setup."""
- assert setup_component(self.hass, introduction.DOMAIN, {})
diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py
index 28d688b2080..366a5f32bc8 100644
--- a/tests/components/light/test_init.py
+++ b/tests/components/light/test_init.py
@@ -7,7 +7,7 @@ from io import StringIO
import pytest
-from homeassistant import core, loader
+from homeassistant import core
from homeassistant.exceptions import Unauthorized
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.const import (
@@ -121,7 +121,7 @@ class TestLight(unittest.TestCase):
def test_services(self):
"""Test the provided services."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
assert setup_component(self.hass, light.DOMAIN,
@@ -161,6 +161,19 @@ class TestLight(unittest.TestCase):
assert not light.is_on(self.hass, dev2.entity_id)
assert not light.is_on(self.hass, dev3.entity_id)
+ # turn off all lights by setting brightness to 0
+ common.turn_on(self.hass)
+
+ self.hass.block_till_done()
+
+ common.turn_on(self.hass, brightness=0)
+
+ self.hass.block_till_done()
+
+ assert not light.is_on(self.hass, dev1.entity_id)
+ assert not light.is_on(self.hass, dev2.entity_id)
+ assert not light.is_on(self.hass, dev3.entity_id)
+
# toggle all lights
common.toggle(self.hass)
@@ -207,6 +220,32 @@ class TestLight(unittest.TestCase):
light.ATTR_HS_COLOR: (71.059, 100),
} == data
+ # Ensure attributes are filtered when light is turned off
+ common.turn_on(self.hass, dev1.entity_id,
+ transition=10, brightness=0, color_name='blue')
+ common.turn_on(
+ self.hass, dev2.entity_id, brightness=0, rgb_color=(255, 255, 255),
+ white_value=0)
+ common.turn_on(self.hass, dev3.entity_id, brightness=0,
+ xy_color=(.4, .6))
+
+ self.hass.block_till_done()
+
+ assert not light.is_on(self.hass, dev1.entity_id)
+ assert not light.is_on(self.hass, dev2.entity_id)
+ assert not light.is_on(self.hass, dev3.entity_id)
+
+ _, data = dev1.last_call('turn_off')
+ assert {
+ light.ATTR_TRANSITION: 10,
+ } == data
+
+ _, data = dev2.last_call('turn_off')
+ assert {} == data
+
+ _, data = dev3.last_call('turn_off')
+ assert {} == data
+
# One of the light profiles
prof_name, prof_h, prof_s, prof_bri = 'relax', 35.932, 69.412, 144
@@ -269,7 +308,7 @@ class TestLight(unittest.TestCase):
def test_broken_light_profiles(self):
"""Test light profiles."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE)
@@ -284,7 +323,7 @@ class TestLight(unittest.TestCase):
def test_light_profiles(self):
"""Test light profiles."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE)
@@ -292,6 +331,7 @@ class TestLight(unittest.TestCase):
with open(user_light_file, 'w') as user_file:
user_file.write('id,x,y,brightness\n')
user_file.write('test,.4,.6,100\n')
+ user_file.write('test_off,0,0,0\n')
assert setup_component(
self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}}
@@ -305,14 +345,24 @@ class TestLight(unittest.TestCase):
_, data = dev1.last_call('turn_on')
+ assert light.is_on(self.hass, dev1.entity_id)
assert {
light.ATTR_HS_COLOR: (71.059, 100),
light.ATTR_BRIGHTNESS: 100
} == data
+ common.turn_on(self.hass, dev1.entity_id, profile='test_off')
+
+ self.hass.block_till_done()
+
+ _, data = dev1.last_call('turn_off')
+
+ assert not light.is_on(self.hass, dev1.entity_id)
+ assert {} == data
+
def test_default_profiles_group(self):
"""Test default turn-on light profile for all lights."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE)
@@ -324,10 +374,10 @@ class TestLight(unittest.TestCase):
return True
return real_isfile(path)
- def _mock_open(path):
+ def _mock_open(path, *args, **kwargs):
if path == user_light_file:
return StringIO(profile_data)
- return real_open(path)
+ return real_open(path, *args, **kwargs)
profile_data = "id,x,y,brightness\n" +\
"group.all_lights.default,.4,.6,99\n"
@@ -350,7 +400,7 @@ class TestLight(unittest.TestCase):
def test_default_profiles_light(self):
"""Test default turn-on light profile for a specific light."""
- platform = loader.get_component(self.hass, 'light.test')
+ platform = getattr(self.hass.components, 'test.light')
platform.init()
user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE)
@@ -362,10 +412,10 @@ class TestLight(unittest.TestCase):
return True
return real_isfile(path)
- def _mock_open(path):
+ def _mock_open(path, *args, **kwargs):
if path == user_light_file:
return StringIO(profile_data)
- return real_open(path)
+ return real_open(path, *args, **kwargs)
profile_data = "id,x,y,brightness\n" +\
"group.all_lights.default,.3,.5,200\n" +\
diff --git a/tests/components/light/test_tradfri.py b/tests/components/light/test_tradfri.py
index 337031cf92c..37d3ec322ff 100644
--- a/tests/components/light/test_tradfri.py
+++ b/tests/components/light/test_tradfri.py
@@ -35,20 +35,12 @@ TURN_ON_TEST_CASES = [
'brightness': 100
}
],
- # Brightness == 0
+ # Brightness == 1
[
{'can_set_dimmer': True},
- {'brightness': 0},
+ {'brightness': 1},
{
- 'brightness': 0
- }
- ],
- # Brightness < 0
- [
- {'can_set_dimmer': True},
- {'brightness': -1},
- {
- 'brightness': 0
+ 'brightness': 1
}
],
# Brightness > 254
diff --git a/tests/components/locative/test_init.py b/tests/components/locative/test_init.py
index f757080eadc..6d541cac653 100644
--- a/tests/components/locative/test_init.py
+++ b/tests/components/locative/test_init.py
@@ -22,15 +22,16 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture
-def locative_client(loop, hass, hass_client):
+async def locative_client(loop, hass, hass_client):
"""Locative mock client."""
- assert loop.run_until_complete(async_setup_component(
+ assert await async_setup_component(
hass, DOMAIN, {
DOMAIN: {}
- }))
+ })
+ await hass.async_block_till_done()
with patch('homeassistant.components.device_tracker.update_config'):
- yield loop.run_until_complete(hass_client())
+ return await hass_client()
@pytest.fixture
@@ -45,6 +46,7 @@ async def webhook_id(hass, locative_client):
result = await hass.config_entries.flow.async_configure(
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ await hass.async_block_till_done()
return result['result'].data['webhook_id']
diff --git a/tests/components/lock/common.py b/tests/components/lock/common.py
index 2150b3cb894..c5a71a3eb96 100644
--- a/tests/components/lock/common.py
+++ b/tests/components/lock/common.py
@@ -6,6 +6,7 @@ components. Instead call the service directly.
from homeassistant.components.lock import DOMAIN
from homeassistant.const import (
ATTR_CODE, ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN)
+from homeassistant.core import callback
from homeassistant.loader import bind_hass
@@ -21,6 +22,19 @@ def lock(hass, entity_id=None, code=None):
hass.services.call(DOMAIN, SERVICE_LOCK, data)
+@callback
+@bind_hass
+def async_lock(hass, entity_id=None, code=None):
+ """Lock all or specified locks."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_LOCK, data))
+
+
@bind_hass
def unlock(hass, entity_id=None, code=None):
"""Unlock all or specified locks."""
@@ -33,6 +47,19 @@ def unlock(hass, entity_id=None, code=None):
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
+@callback
+@bind_hass
+def async_unlock(hass, entity_id=None, code=None):
+ """Lock all or specified locks."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_UNLOCK, data))
+
+
@bind_hass
def open_lock(hass, entity_id=None, code=None):
"""Open all or specified locks."""
@@ -43,3 +70,16 @@ def open_lock(hass, entity_id=None, code=None):
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_OPEN, data)
+
+
+@callback
+@bind_hass
+def async_open_lock(hass, entity_id=None, code=None):
+ """Lock all or specified locks."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_OPEN, data))
diff --git a/tests/components/logi_circle/__init__.py b/tests/components/logi_circle/__init__.py
new file mode 100644
index 00000000000..d2e2fbb8fdb
--- /dev/null
+++ b/tests/components/logi_circle/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Logi Circle component."""
diff --git a/tests/components/logi_circle/test_config_flow.py b/tests/components/logi_circle/test_config_flow.py
new file mode 100644
index 00000000000..c209b8a143d
--- /dev/null
+++ b/tests/components/logi_circle/test_config_flow.py
@@ -0,0 +1,201 @@
+"""Tests for Logi Circle config flow."""
+import asyncio
+from unittest.mock import Mock, patch
+
+import pytest
+
+from homeassistant import data_entry_flow
+from homeassistant.components.logi_circle import config_flow
+from homeassistant.components.logi_circle.config_flow import (
+ DOMAIN, LogiCircleAuthCallbackView)
+from homeassistant.setup import async_setup_component
+
+from tests.common import MockDependency, mock_coro
+
+
+class AuthorizationFailed(Exception):
+ """Dummy Exception."""
+
+
+class MockRequest():
+ """Mock request passed to HomeAssistantView."""
+
+ def __init__(self, hass, query):
+ """Init request object."""
+ self.app = {"hass": hass}
+ self.query = query
+
+
+def init_config_flow(hass):
+ """Init a configuration flow."""
+ config_flow.register_flow_implementation(hass,
+ DOMAIN,
+ client_id='id',
+ client_secret='secret',
+ api_key='123',
+ redirect_uri='http://example.com',
+ sensors=None)
+ flow = config_flow.LogiCircleFlowHandler()
+ flow._get_authorization_url = Mock( # pylint: disable=W0212
+ return_value='http://example.com')
+ flow.hass = hass
+ return flow
+
+
+@pytest.fixture
+def mock_logi_circle():
+ """Mock logi_circle."""
+ with MockDependency('logi_circle', 'exception') as mock_logi_circle_:
+ mock_logi_circle_.exception.AuthorizationFailed = AuthorizationFailed
+ mock_logi_circle_.LogiCircle().authorize = Mock(
+ return_value=mock_coro(return_value=True))
+ mock_logi_circle_.LogiCircle().close = Mock(
+ return_value=mock_coro(return_value=True))
+ mock_logi_circle_.LogiCircle().account = mock_coro(
+ return_value={'accountId': 'testId'})
+ mock_logi_circle_.LogiCircle().authorize_url = 'http://authorize.url'
+ yield mock_logi_circle_
+
+
+async def test_step_import(hass, mock_logi_circle): # pylint: disable=W0621
+ """Test that we trigger import when configuring with client."""
+ flow = init_config_flow(hass)
+
+ result = await flow.async_step_import()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'auth'
+
+
+async def test_full_flow_implementation(hass, mock_logi_circle): # noqa pylint: disable=W0621
+ """Test registering an implementation and finishing flow works."""
+ config_flow.register_flow_implementation(
+ hass,
+ 'test-other',
+ client_id=None,
+ client_secret=None,
+ api_key=None,
+ redirect_uri=None,
+ sensors=None)
+ flow = init_config_flow(hass)
+
+ result = await flow.async_step_user()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'user'
+
+ result = await flow.async_step_user({'flow_impl': 'test-other'})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'auth'
+ assert result['description_placeholders'] == {
+ 'authorization_url': 'http://example.com',
+ }
+
+ result = await flow.async_step_code('123ABC')
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['title'] == 'Logi Circle ({})'.format('testId')
+
+
+async def test_we_reprompt_user_to_follow_link(hass):
+ """Test we prompt user to follow link if previously prompted."""
+ flow = init_config_flow(hass)
+
+ result = await flow.async_step_auth('dummy')
+ assert result['errors']['base'] == 'follow_link'
+
+
+async def test_abort_if_no_implementation_registered(hass):
+ """Test we abort if no implementation is registered."""
+ flow = config_flow.LogiCircleFlowHandler()
+ flow.hass = hass
+
+ result = await flow.async_step_user()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'no_flows'
+
+
+async def test_abort_if_already_setup(hass):
+ """Test we abort if Logi Circle is already setup."""
+ flow = init_config_flow(hass)
+
+ with patch.object(hass.config_entries, 'async_entries', return_value=[{}]):
+ result = await flow.async_step_user()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'already_setup'
+
+ with patch.object(hass.config_entries, 'async_entries', return_value=[{}]):
+ result = await flow.async_step_import()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'already_setup'
+
+ with patch.object(hass.config_entries, 'async_entries', return_value=[{}]):
+ result = await flow.async_step_code()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'already_setup'
+
+ with patch.object(hass.config_entries, 'async_entries', return_value=[{}]):
+ result = await flow.async_step_auth()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'external_setup'
+
+
+@pytest.mark.parametrize('side_effect,error',
+ [(asyncio.TimeoutError, 'auth_timeout'),
+ (AuthorizationFailed, 'auth_error')])
+async def test_abort_if_authorize_fails(hass, mock_logi_circle, side_effect, error): # noqa pylint: disable=W0621
+ """Test we abort if authorizing fails."""
+ flow = init_config_flow(hass)
+ mock_logi_circle.LogiCircle().authorize.side_effect = side_effect
+
+ result = await flow.async_step_code('123ABC')
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'external_error'
+
+ result = await flow.async_step_auth()
+ assert result['errors']['base'] == error
+
+
+async def test_not_pick_implementation_if_only_one(hass):
+ """Test we bypass picking implementation if we have one flow_imp."""
+ flow = init_config_flow(hass)
+
+ result = await flow.async_step_user()
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+ assert result['step_id'] == 'auth'
+
+
+async def test_gen_auth_url(hass, mock_logi_circle): # pylint: disable=W0621
+ """Test generating authorize URL from Logi Circle API."""
+ config_flow.register_flow_implementation(hass,
+ 'test-auth-url',
+ client_id='id',
+ client_secret='secret',
+ api_key='123',
+ redirect_uri='http://example.com',
+ sensors=None)
+ flow = config_flow.LogiCircleFlowHandler()
+ flow.hass = hass
+ flow.flow_impl = 'test-auth-url'
+ await async_setup_component(hass, 'http', {})
+
+ result = flow._get_authorization_url() # pylint: disable=W0212
+ assert result == 'http://authorize.url'
+
+
+async def test_callback_view_rejects_missing_code(hass):
+ """Test the auth callback view rejects requests with no code."""
+ view = LogiCircleAuthCallbackView()
+ resp = await view.get(MockRequest(hass, {}))
+
+ assert resp.status == 400
+
+
+async def test_callback_view_accepts_code(hass, mock_logi_circle): # noqa pylint: disable=W0621
+ """Test the auth callback view handles requests with auth code."""
+ init_config_flow(hass)
+ view = LogiCircleAuthCallbackView()
+
+ resp = await view.get(MockRequest(hass, {"code": "456"}))
+ assert resp.status == 200
+
+ await hass.async_block_till_done()
+ mock_logi_circle.LogiCircle.return_value.authorize.assert_called_with(
+ '456')
diff --git a/tests/components/mobile_app/__init__.py b/tests/components/mobile_app/__init__.py
index cf617ff0528..98c7a20b059 100644
--- a/tests/components/mobile_app/__init__.py
+++ b/tests/components/mobile_app/__init__.py
@@ -55,7 +55,7 @@ async def webhook_client(hass, aiohttp_client, hass_storage, hass_admin_user):
}
await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
-
+ await hass.async_block_till_done()
return await aiohttp_client(hass.http.app)
@@ -63,6 +63,7 @@ async def webhook_client(hass, aiohttp_client, hass_storage, hass_admin_user):
async def authed_api_client(hass, hass_client):
"""Provide an authenticated client for mobile_app to use."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
+ await hass.async_block_till_done()
return await hass_client()
@@ -70,3 +71,4 @@ async def authed_api_client(hass, hass_client):
async def setup_ws(hass):
"""Configure the websocket_api component."""
assert await async_setup_component(hass, 'websocket_api', {})
+ await hass.async_block_till_done()
diff --git a/tests/components/mobile_app/test_http_api.py b/tests/components/mobile_app/test_http_api.py
index eb9d1f54d93..dc51b850a16 100644
--- a/tests/components/mobile_app/test_http_api.py
+++ b/tests/components/mobile_app/test_http_api.py
@@ -80,28 +80,3 @@ async def test_registration(hass, hass_client): # noqa: F811
decrypted_data = decrypted_data.decode("utf-8")
assert json.loads(decrypted_data) == {'one': 'Hello world'}
-
-
-async def test_register_invalid_component(authed_api_client): # noqa: F811
- """Test that registration with invalid component fails."""
- resp = await authed_api_client.post(
- '/api/mobile_app/registrations', json={
- 'app_component': 'will_never_be_valid',
- 'app_data': {'foo': 'bar'},
- 'app_id': 'io.homeassistant.mobile_app_test',
- 'app_name': 'Mobile App Tests',
- 'app_version': '1.0.0',
- 'device_name': 'Test 1',
- 'manufacturer': 'mobile_app',
- 'model': 'Test',
- 'os_name': 'Linux',
- 'os_version': '1.0',
- 'supports_encryption': True
- }
- )
-
- assert resp.status == 400
- register_json = await resp.json()
- assert 'error' in register_json
- assert register_json['success'] is False
- assert register_json['error']['code'] == 'invalid_component'
diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py
index 742aafba8dc..6efaedd270b 100644
--- a/tests/components/mqtt/test_alarm_control_panel.py
+++ b/tests/components/mqtt/test_alarm_control_panel.py
@@ -288,15 +288,64 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
self.mock_publish.async_publish.assert_called_once_with(
'alarm/command', 'DISARM', 0, False)
- def test_disarm_not_publishes_mqtt_with_invalid_code(self):
- """Test not publishing of MQTT messages with invalid code."""
+ def test_disarm_publishes_mqtt_with_template(self):
+ """Test publishing of MQTT messages while disarmed.
+
+ When command_template set to output json
+ """
assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
- 'code': '1234'
+ 'code': '1234',
+ 'command_template': '{\"action\":\"{{ action }}\",'
+ '\"code\":\"{{ code }}\"}',
+ }
+ })
+
+ common.alarm_disarm(self.hass, 1234)
+ self.hass.block_till_done()
+ self.mock_publish.async_publish.assert_called_once_with(
+ 'alarm/command', '{\"action\":\"DISARM\",\"code\":\"1234\"}',
+ 0,
+ False)
+
+ def test_disarm_publishes_mqtt_when_code_not_req(self):
+ """Test publishing of MQTT messages while disarmed.
+
+ When code_disarm_required = False
+ """
+ assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
+ alarm_control_panel.DOMAIN: {
+ 'platform': 'mqtt',
+ 'name': 'test',
+ 'state_topic': 'alarm/state',
+ 'command_topic': 'alarm/command',
+ 'code': '1234',
+ 'code_disarm_required': False
+ }
+ })
+
+ common.alarm_disarm(self.hass)
+ self.hass.block_till_done()
+ self.mock_publish.async_publish.assert_called_once_with(
+ 'alarm/command', 'DISARM', 0, False)
+
+ def test_disarm_not_publishes_mqtt_with_invalid_code_when_req(self):
+ """Test not publishing of MQTT messages with invalid code.
+
+ When code_disarm_required = True
+ """
+ assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
+ alarm_control_panel.DOMAIN: {
+ 'platform': 'mqtt',
+ 'name': 'test',
+ 'state_topic': 'alarm/state',
+ 'command_topic': 'alarm/command',
+ 'code': '1234',
+ 'code_disarm_required': True
}
})
@@ -373,6 +422,33 @@ async def test_setting_attribute_via_mqtt_json_message(hass, mqtt_mock):
assert '100' == state.attributes.get('val')
+async def test_update_state_via_state_topic_template(hass, mqtt_mock):
+ """Test updating with template_value via state topic."""
+ assert await async_setup_component(hass, alarm_control_panel.DOMAIN, {
+ alarm_control_panel.DOMAIN: {
+ 'platform': 'mqtt',
+ 'name': 'test',
+ 'command_topic': 'test-topic',
+ 'state_topic': 'test-topic',
+ 'value_template': '\
+ {% if (value | int) == 100 %}\
+ armed_away\
+ {% else %}\
+ disarmed\
+ {% endif %}'
+ }
+ })
+
+ state = hass.states.get('alarm_control_panel.test')
+ assert STATE_UNKNOWN == state.state
+
+ async_fire_mqtt_message(hass, 'test-topic', '100')
+ await hass.async_block_till_done()
+
+ state = hass.states.get('alarm_control_panel.test')
+ assert STATE_ALARM_ARMED_AWAY == state.state
+
+
async def test_update_with_json_attrs_not_dict(hass, mqtt_mock, caplog):
"""Test attributes get extracted from a JSON result."""
assert await async_setup_component(hass, alarm_control_panel.DOMAIN, {
diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py
index 7bdfe8f452f..a8e1ae6111e 100644
--- a/tests/components/mqtt/test_climate.py
+++ b/tests/components/mqtt/test_climate.py
@@ -15,7 +15,8 @@ from homeassistant.components.climate.const import (
SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE,
SUPPORT_FAN_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, STATE_AUTO,
- STATE_COOL, STATE_HEAT, STATE_DRY, STATE_FAN_ONLY)
+ STATE_COOL, STATE_HEAT, STATE_DRY, STATE_FAN_ONLY,
+ SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_TARGET_TEMPERATURE_HIGH)
from homeassistant.components.mqtt.discovery import async_start
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.setup import setup_component
@@ -35,6 +36,8 @@ DEFAULT_CONFIG = {
'name': 'test',
'mode_command_topic': 'mode-topic',
'temperature_command_topic': 'temperature-topic',
+ 'temperature_low_command_topic': 'temperature-low-topic',
+ 'temperature_high_command_topic': 'temperature-high-topic',
'fan_mode_command_topic': 'fan-mode-topic',
'swing_mode_command_topic': 'swing-mode-topic',
'away_mode_command_topic': 'away-mode-topic',
@@ -75,7 +78,9 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
support = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_SWING_MODE | SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE |
- SUPPORT_HOLD_MODE | SUPPORT_AUX_HEAT)
+ SUPPORT_HOLD_MODE | SUPPORT_AUX_HEAT |
+ SUPPORT_TARGET_TEMPERATURE_LOW |
+ SUPPORT_TARGET_TEMPERATURE_HIGH)
assert state.attributes.get("supported_features") == support
@@ -341,6 +346,66 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
assert 1701 == state.attributes.get('temperature')
+ def test_set_target_temperature_low_high(self):
+ """Test setting the low/high target temperature."""
+ assert setup_component(self.hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
+
+ common.set_temperature(self.hass, target_temp_low=20,
+ target_temp_high=23,
+ entity_id=ENTITY_CLIMATE)
+ self.hass.block_till_done()
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ print(state.attributes)
+ assert 20 == state.attributes.get('target_temp_low')
+ assert 23 == state.attributes.get('target_temp_high')
+ self.mock_publish.async_publish.assert_any_call(
+ 'temperature-low-topic', 20, 0, False)
+ self.mock_publish.async_publish.assert_any_call(
+ 'temperature-high-topic', 23, 0, False)
+
+ def test_set_target_temperature_low_highpessimistic(self):
+ """Test setting the low/high target temperature."""
+ config = copy.deepcopy(DEFAULT_CONFIG)
+ config['climate']['temperature_low_state_topic'] = \
+ 'temperature-low-state'
+ config['climate']['temperature_high_state_topic'] = \
+ 'temperature-high-state'
+ assert setup_component(self.hass, CLIMATE_DOMAIN, config)
+
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ assert state.attributes.get('target_temp_low') is None
+ assert state.attributes.get('target_temp_high') is None
+ self.hass.block_till_done()
+ common.set_temperature(self.hass, target_temp_low=20,
+ target_temp_high=23,
+ entity_id=ENTITY_CLIMATE)
+ self.hass.block_till_done()
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ assert state.attributes.get('target_temp_low') is None
+ assert state.attributes.get('target_temp_high') is None
+
+ fire_mqtt_message(self.hass, 'temperature-low-state', '1701')
+ self.hass.block_till_done()
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ assert 1701 == state.attributes.get('target_temp_low')
+ assert state.attributes.get('target_temp_high') is None
+
+ fire_mqtt_message(self.hass, 'temperature-high-state', '1703')
+ self.hass.block_till_done()
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ assert 1701 == state.attributes.get('target_temp_low')
+ assert 1703 == state.attributes.get('target_temp_high')
+
+ fire_mqtt_message(self.hass, 'temperature-low-state', 'not a number')
+ self.hass.block_till_done()
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ assert 1701 == state.attributes.get('target_temp_low')
+
+ fire_mqtt_message(self.hass, 'temperature-high-state', 'not a number')
+ self.hass.block_till_done()
+ state = self.hass.states.get(ENTITY_CLIMATE)
+ assert 1703 == state.attributes.get('target_temp_high')
+
def test_receive_mqtt_temperature(self):
"""Test getting the current temperature via MQTT."""
config = copy.deepcopy(DEFAULT_CONFIG)
diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py
index 9d822ba854b..64196c9febd 100644
--- a/tests/components/mqtt/test_config_flow.py
+++ b/tests/components/mqtt/test_config_flow.py
@@ -81,6 +81,7 @@ async def test_manual_config_set(hass, mock_try_connection,
"""Test we ignore entry if manual config available."""
assert await async_setup_component(
hass, 'mqtt', {'mqtt': {'broker': 'bla'}})
+ await hass.async_block_till_done()
assert len(mock_finish_setup.mock_calls) == 1
mock_try_connection.return_value = True
diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py
index ffc385021d7..ba72db52a8f 100644
--- a/tests/components/mqtt/test_discovery.py
+++ b/tests/components/mqtt/test_discovery.py
@@ -1,5 +1,4 @@
"""The tests for the MQTT discovery."""
-import asyncio
from unittest.mock import patch
from homeassistant.components import mqtt
@@ -10,8 +9,7 @@ from homeassistant.const import STATE_OFF, STATE_ON
from tests.common import MockConfigEntry, async_fire_mqtt_message, mock_coro
-@asyncio.coroutine
-def test_subscribing_config_topic(hass, mqtt_mock):
+async def test_subscribing_config_topic(hass, mqtt_mock):
"""Test setting up discovery."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
mqtt.CONF_BROKER: 'test-broker'
@@ -19,7 +17,7 @@ def test_subscribing_config_topic(hass, mqtt_mock):
hass_config = {}
discovery_topic = 'homeassistant'
- yield from async_start(hass, discovery_topic, hass_config, entry)
+ await async_start(hass, discovery_topic, hass_config, entry)
assert mqtt_mock.async_subscribe.called
call_args = mqtt_mock.async_subscribe.mock_calls[0][1]
@@ -27,57 +25,57 @@ def test_subscribing_config_topic(hass, mqtt_mock):
assert call_args[2] == 0
-@patch('homeassistant.components.mqtt.discovery.async_load_platform')
-@asyncio.coroutine
-def test_invalid_topic(mock_load_platform, hass, mqtt_mock):
+async def test_invalid_topic(hass, mqtt_mock):
"""Test sending to invalid topic."""
- entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
- mqtt.CONF_BROKER: 'test-broker'
- })
+ with patch('homeassistant.components.mqtt.discovery.async_load_platform')\
+ as mock_load_platform:
+ entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
+ mqtt.CONF_BROKER: 'test-broker'
+ })
- mock_load_platform.return_value = mock_coro()
- yield from async_start(hass, 'homeassistant', {}, entry)
+ mock_load_platform.return_value = mock_coro()
+ await async_start(hass, 'homeassistant', {}, entry)
- async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/not_config',
- '{}')
- yield from hass.async_block_till_done()
- assert not mock_load_platform.called
+ async_fire_mqtt_message(
+ hass, 'homeassistant/binary_sensor/bla/not_config', '{}')
+ await hass.async_block_till_done()
+ assert not mock_load_platform.called
-@patch('homeassistant.components.mqtt.discovery.async_load_platform')
-@asyncio.coroutine
-def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog):
+async def test_invalid_json(hass, mqtt_mock, caplog):
"""Test sending in invalid JSON."""
- entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
- mqtt.CONF_BROKER: 'test-broker'
- })
+ with patch('homeassistant.components.mqtt.discovery.async_load_platform')\
+ as mock_load_platform:
+ entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
+ mqtt.CONF_BROKER: 'test-broker'
+ })
- mock_load_platform.return_value = mock_coro()
- yield from async_start(hass, 'homeassistant', {}, entry)
+ mock_load_platform.return_value = mock_coro()
+ await async_start(hass, 'homeassistant', {}, entry)
- async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
- 'not json')
- yield from hass.async_block_till_done()
- assert 'Unable to parse JSON' in caplog.text
- assert not mock_load_platform.called
+ async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
+ 'not json')
+ await hass.async_block_till_done()
+ assert 'Unable to parse JSON' in caplog.text
+ assert not mock_load_platform.called
-@patch('homeassistant.components.mqtt.discovery.async_load_platform')
-@asyncio.coroutine
-def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog):
+async def test_only_valid_components(hass, mqtt_mock, caplog):
"""Test for a valid component."""
- entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ with patch('homeassistant.components.mqtt.discovery.async_load_platform')\
+ as mock_load_platform:
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
- invalid_component = "timer"
+ invalid_component = "timer"
- mock_load_platform.return_value = mock_coro()
- yield from async_start(hass, 'homeassistant', {}, entry)
+ mock_load_platform.return_value = mock_coro()
+ await async_start(hass, 'homeassistant', {}, entry)
- async_fire_mqtt_message(hass, 'homeassistant/{}/bla/config'.format(
- invalid_component
- ), '{}')
+ async_fire_mqtt_message(hass, 'homeassistant/{}/bla/config'.format(
+ invalid_component
+ ), '{}')
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
assert 'Component {} is not supported'.format(
invalid_component
@@ -86,16 +84,15 @@ def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog):
assert not mock_load_platform.called
-@asyncio.coroutine
-def test_correct_config_discovery(hass, mqtt_mock, caplog):
+async def test_correct_config_discovery(hass, mqtt_mock, caplog):
"""Test sending in correct JSON."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }')
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('binary_sensor.beer')
@@ -104,17 +101,16 @@ def test_correct_config_discovery(hass, mqtt_mock, caplog):
assert ('binary_sensor', 'bla') in hass.data[ALREADY_DISCOVERED]
-@asyncio.coroutine
-def test_discover_fan(hass, mqtt_mock, caplog):
+async def test_discover_fan(hass, mqtt_mock, caplog):
"""Test discovering an MQTT fan."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/fan/bla/config',
('{ "name": "Beer",'
' "command_topic": "test_topic" }'))
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('fan.beer')
@@ -123,12 +119,11 @@ def test_discover_fan(hass, mqtt_mock, caplog):
assert ('fan', 'bla') in hass.data[ALREADY_DISCOVERED]
-@asyncio.coroutine
-def test_discover_climate(hass, mqtt_mock, caplog):
+async def test_discover_climate(hass, mqtt_mock, caplog):
"""Test discovering an MQTT climate component."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
data = (
'{ "name": "ClimateTest",'
@@ -137,7 +132,7 @@ def test_discover_climate(hass, mqtt_mock, caplog):
)
async_fire_mqtt_message(hass, 'homeassistant/climate/bla/config', data)
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('climate.ClimateTest')
@@ -146,12 +141,11 @@ def test_discover_climate(hass, mqtt_mock, caplog):
assert ('climate', 'bla') in hass.data[ALREADY_DISCOVERED]
-@asyncio.coroutine
-def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
+async def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
"""Test discovering an MQTT alarm control panel component."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
data = (
'{ "name": "AlarmControlPanelTest",'
@@ -161,7 +155,7 @@ def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
async_fire_mqtt_message(
hass, 'homeassistant/alarm_control_panel/bla/config', data)
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('alarm_control_panel.AlarmControlPanelTest')
@@ -170,16 +164,15 @@ def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
assert ('alarm_control_panel', 'bla') in hass.data[ALREADY_DISCOVERED]
-@asyncio.coroutine
-def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
+async def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
"""Test sending in correct JSON with optional node_id included."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/my_node_id/bla'
'/config', '{ "name": "Beer" }')
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('binary_sensor.beer')
@@ -188,18 +181,17 @@ def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
assert ('binary_sensor', 'my_node_id bla') in hass.data[ALREADY_DISCOVERED]
-@asyncio.coroutine
-def test_non_duplicate_discovery(hass, mqtt_mock, caplog):
+async def test_non_duplicate_discovery(hass, mqtt_mock, caplog):
"""Test for a non duplicate component."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }')
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }')
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('binary_sensor.beer')
state_duplicate = hass.states.get('binary_sensor.beer1')
@@ -211,12 +203,11 @@ def test_non_duplicate_discovery(hass, mqtt_mock, caplog):
'binary_sensor bla' in caplog.text
-@asyncio.coroutine
-def test_discovery_expansion(hass, mqtt_mock, caplog):
+async def test_discovery_expansion(hass, mqtt_mock, caplog):
"""Test expansion of abbreviated discovery payload."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
- yield from async_start(hass, 'homeassistant', {}, entry)
+ await async_start(hass, 'homeassistant', {}, entry)
data = (
'{ "~": "some/base/topic",'
@@ -235,7 +226,7 @@ def test_discovery_expansion(hass, mqtt_mock, caplog):
async_fire_mqtt_message(
hass, 'homeassistant/switch/bla/config', data)
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('switch.DiscoveryExpansionTest1')
assert state is not None
@@ -245,8 +236,146 @@ def test_discovery_expansion(hass, mqtt_mock, caplog):
async_fire_mqtt_message(hass, 'test_topic/some/base/topic',
'ON')
- yield from hass.async_block_till_done()
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
state = hass.states.get('switch.DiscoveryExpansionTest1')
assert state.state == STATE_ON
+
+
+async def test_implicit_state_topic_alarm(hass, mqtt_mock, caplog):
+ """Test implicit state topic for alarm_control_panel."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Test1",'
+ ' "command_topic": "homeassistant/alarm_control_panel/bla/cmnd"'
+ '}'
+ )
+
+ async_fire_mqtt_message(
+ hass, 'homeassistant/alarm_control_panel/bla/config', data)
+ await hass.async_block_till_done()
+ assert (
+ 'implicit state_topic is deprecated, add '
+ '"state_topic":"homeassistant/alarm_control_panel/bla/state"'
+ in caplog.text)
+
+ state = hass.states.get('alarm_control_panel.Test1')
+ assert state is not None
+ assert state.name == 'Test1'
+ assert ('alarm_control_panel', 'bla') in hass.data[ALREADY_DISCOVERED]
+ assert state.state == 'unknown'
+
+ async_fire_mqtt_message(
+ hass, 'homeassistant/alarm_control_panel/bla/state', 'armed_away')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('alarm_control_panel.Test1')
+ assert state.state == 'armed_away'
+
+
+async def test_implicit_state_topic_binary_sensor(hass, mqtt_mock, caplog):
+ """Test implicit state topic for binary_sensor."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Test1"'
+ '}'
+ )
+
+ async_fire_mqtt_message(
+ hass, 'homeassistant/binary_sensor/bla/config', data)
+ await hass.async_block_till_done()
+ assert (
+ 'implicit state_topic is deprecated, add '
+ '"state_topic":"homeassistant/binary_sensor/bla/state"'
+ in caplog.text)
+
+ state = hass.states.get('binary_sensor.Test1')
+ assert state is not None
+ assert state.name == 'Test1'
+ assert ('binary_sensor', 'bla') in hass.data[ALREADY_DISCOVERED]
+ assert state.state == 'off'
+
+ async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/state',
+ 'ON')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('binary_sensor.Test1')
+ assert state.state == 'on'
+
+
+async def test_implicit_state_topic_sensor(hass, mqtt_mock, caplog):
+ """Test implicit state topic for sensor."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Test1"'
+ '}'
+ )
+
+ async_fire_mqtt_message(
+ hass, 'homeassistant/sensor/bla/config', data)
+ await hass.async_block_till_done()
+ assert (
+ 'implicit state_topic is deprecated, add '
+ '"state_topic":"homeassistant/sensor/bla/state"'
+ in caplog.text)
+
+ state = hass.states.get('sensor.Test1')
+ assert state is not None
+ assert state.name == 'Test1'
+ assert ('sensor', 'bla') in hass.data[ALREADY_DISCOVERED]
+ assert state.state == 'unknown'
+
+ async_fire_mqtt_message(hass, 'homeassistant/sensor/bla/state',
+ '1234')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('sensor.Test1')
+ assert state.state == '1234'
+
+
+async def test_no_implicit_state_topic_switch(hass, mqtt_mock, caplog):
+ """Test no implicit state topic for switch."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Test1",'
+ ' "command_topic": "cmnd"'
+ '}'
+ )
+
+ async_fire_mqtt_message(
+ hass, 'homeassistant/switch/bla/config', data)
+ await hass.async_block_till_done()
+ assert (
+ 'implicit state_topic is deprecated'
+ not in caplog.text)
+
+ state = hass.states.get('switch.Test1')
+ assert state is not None
+ assert state.name == 'Test1'
+ assert ('switch', 'bla') in hass.data[ALREADY_DISCOVERED]
+ assert state.state == 'off'
+ assert state.attributes['assumed_state'] is True
+
+ async_fire_mqtt_message(hass, 'homeassistant/switch/bla/state',
+ 'ON')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('switch.Test1')
+ assert state.state == 'off'
diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py
index 5c441a68bea..dc9299e4a35 100644
--- a/tests/components/mqtt/test_init.py
+++ b/tests/components/mqtt/test_init.py
@@ -12,6 +12,7 @@ from homeassistant.const import (
ATTR_DOMAIN, ATTR_SERVICE, EVENT_CALL_SERVICE, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.setup import async_setup_component
+from homeassistant.exceptions import ConfigEntryNotReady
from tests.common import (
MockConfigEntry, async_fire_mqtt_message, async_mock_mqtt_component,
@@ -27,8 +28,7 @@ def mock_MQTT():
yield mock_MQTT
-@asyncio.coroutine
-def async_mock_mqtt_client(hass, config=None):
+async def async_mock_mqtt_client(hass, config=None):
"""Mock the MQTT paho client."""
if config is None:
config = {mqtt.CONF_BROKER: 'mock-broker'}
@@ -38,10 +38,11 @@ def async_mock_mqtt_client(hass, config=None):
mock_client().subscribe.return_value = (0, 0)
mock_client().unsubscribe.return_value = (0, 0)
mock_client().publish.return_value = (0, 0)
- result = yield from async_setup_component(hass, mqtt.DOMAIN, {
+ result = await async_setup_component(hass, mqtt.DOMAIN, {
mqtt.DOMAIN: config
})
assert result
+ await hass.async_block_till_done()
return mock_client()
@@ -621,6 +622,19 @@ async def test_setup_fails_if_no_connect_broker(hass):
assert not await mqtt.async_setup_entry(hass, entry)
+async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass):
+ """Test for setup failure if connection to broker is missing."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
+ mqtt.CONF_BROKER: 'test-broker'
+ })
+
+ with mock.patch('paho.mqtt.client.Client') as mock_client:
+ mock_client().connect = mock.Mock(
+ side_effect=OSError("Connection error"))
+ with pytest.raises(ConfigEntryNotReady):
+ await mqtt.async_setup_entry(hass, entry)
+
+
async def test_setup_uses_certificate_on_certificate_set_to_auto(
hass, mock_MQTT):
"""Test setup uses bundled certs when certificate is set to auto."""
diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py
index 172e6dbd8cf..1e325cec5ab 100644
--- a/tests/components/mqtt/test_light_json.py
+++ b/tests/components/mqtt/test_light_json.py
@@ -88,6 +88,7 @@ light:
brightness_scale: 99
"""
import json
+from unittest import mock
from unittest.mock import ANY, patch
from homeassistant.components import light, mqtt
@@ -101,6 +102,19 @@ from homeassistant.setup import async_setup_component
from tests.common import (
MockConfigEntry, async_fire_mqtt_message, async_mock_mqtt_component,
mock_coro, mock_registry)
+from tests.components.light import common
+
+
+class JsonValidator(object):
+ """Helper to compare JSON."""
+
+ def __init__(self, jsondata):
+ """Constructor."""
+ self.jsondata = jsondata
+
+ def __eq__(self, other):
+ """Compare JSON data."""
+ return json.loads(self.jsondata) == json.loads(other)
async def test_fail_setup_if_no_command_topic(hass, mqtt_mock):
@@ -295,7 +309,9 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
'brightness': True,
'color_temp': True,
'effect': True,
+ 'hs': True,
'rgb': True,
+ 'xy': True,
'white_value': True,
'qos': 2
}
@@ -311,6 +327,65 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES)
assert state.attributes.get(ATTR_ASSUMED_STATE)
+ common.async_turn_on(hass, 'light.test')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'test_light_rgb/set', '{"state": "ON"}', 2, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('light.test')
+ assert STATE_ON == state.state
+
+ common.async_turn_off(hass, 'light.test')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'test_light_rgb/set', '{"state": "OFF"}', 2, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('light.test')
+ assert STATE_OFF == state.state
+
+ mqtt_mock.reset_mock()
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, xy_color=[0.123, 0.123])
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, hs_color=[359, 78])
+ common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0],
+ white_value=80)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_has_calls([
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 0, "g": 123, "b": 255,'
+ ' "x": 0.14, "y": 0.131, "h": 210.824, "s": 100.0},'
+ ' "brightness": 50}'),
+ 2, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 255, "g": 56, "b": 59,'
+ ' "x": 0.654, "y": 0.301, "h": 359.0, "s": 78.0},'
+ ' "brightness": 50}'),
+ 2, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 255, "g": 128, "b": 0,'
+ ' "x": 0.611, "y": 0.375, "h": 30.118, "s": 100.0},'
+ ' "white_value": 80}'),
+ 2, False),
+ ], any_order=True)
+
+ state = hass.states.get('light.test')
+ assert STATE_ON == state.state
+ assert (255, 128, 0) == state.attributes['rgb_color']
+ assert 50 == state.attributes['brightness']
+ assert (30.118, 100) == state.attributes['hs_color']
+ assert 80 == state.attributes['white_value']
+ assert (0.611, 0.375) == state.attributes['xy_color']
+
async def test_sending_hs_color(hass, mqtt_mock):
"""Test light.turn_on with hs color sends hs color parameters."""
@@ -320,6 +395,7 @@ async def test_sending_hs_color(hass, mqtt_mock):
'schema': 'json',
'name': 'test',
'command_topic': 'test_light_rgb/set',
+ 'brightness': True,
'hs': True,
}
})
@@ -327,6 +403,170 @@ async def test_sending_hs_color(hass, mqtt_mock):
state = hass.states.get('light.test')
assert STATE_OFF == state.state
+ mqtt_mock.reset_mock()
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, xy_color=[0.123, 0.123])
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, hs_color=[359, 78])
+ common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0],
+ white_value=80)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_has_calls([
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"h": 210.824, "s": 100.0},'
+ ' "brightness": 50}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"h": 359.0, "s": 78.0},'
+ ' "brightness": 50}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"h": 30.118, "s": 100.0},'
+ ' "white_value": 80}'),
+ 0, False),
+ ], any_order=True)
+
+
+async def test_sending_rgb_color_no_brightness(hass, mqtt_mock):
+ """Test light.turn_on with hs color sends rgb color parameters."""
+ assert await async_setup_component(hass, light.DOMAIN, {
+ light.DOMAIN: {
+ 'platform': 'mqtt',
+ 'schema': 'json',
+ 'name': 'test',
+ 'command_topic': 'test_light_rgb/set',
+ 'rgb': True,
+ }
+ })
+
+ state = hass.states.get('light.test')
+ assert STATE_OFF == state.state
+
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, xy_color=[0.123, 0.123])
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, hs_color=[359, 78])
+ common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0],
+ brightness=255)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_has_calls([
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 0, "g": 24, "b": 50}}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 50, "g": 11, "b": 11}}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 255, "g": 128, "b": 0}}'),
+ 0, False),
+ ], any_order=True)
+
+
+async def test_sending_rgb_color_with_brightness(hass, mqtt_mock):
+ """Test light.turn_on with hs color sends rgb color parameters."""
+ assert await async_setup_component(hass, light.DOMAIN, {
+ light.DOMAIN: {
+ 'platform': 'mqtt',
+ 'schema': 'json',
+ 'name': 'test',
+ 'command_topic': 'test_light_rgb/set',
+ 'brightness': True,
+ 'rgb': True,
+ }
+ })
+
+ state = hass.states.get('light.test')
+ assert STATE_OFF == state.state
+
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, xy_color=[0.123, 0.123])
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, hs_color=[359, 78])
+ common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0],
+ white_value=80)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_has_calls([
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 0, "g": 123, "b": 255},'
+ ' "brightness": 50}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 255, "g": 56, "b": 59},'
+ ' "brightness": 50}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"r": 255, "g": 128, "b": 0},'
+ ' "white_value": 80}'),
+ 0, False),
+ ], any_order=True)
+
+
+async def test_sending_xy_color(hass, mqtt_mock):
+ """Test light.turn_on with hs color sends xy color parameters."""
+ assert await async_setup_component(hass, light.DOMAIN, {
+ light.DOMAIN: {
+ 'platform': 'mqtt',
+ 'schema': 'json',
+ 'name': 'test',
+ 'command_topic': 'test_light_rgb/set',
+ 'brightness': True,
+ 'xy': True,
+ }
+ })
+
+ state = hass.states.get('light.test')
+ assert STATE_OFF == state.state
+
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, xy_color=[0.123, 0.123])
+ common.async_turn_on(hass, 'light.test',
+ brightness=50, hs_color=[359, 78])
+ common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0],
+ white_value=80)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_has_calls([
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"x": 0.14, "y": 0.131},'
+ ' "brightness": 50}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"x": 0.654, "y": 0.301},'
+ ' "brightness": 50}'),
+ 0, False),
+ mock.call(
+ 'test_light_rgb/set',
+ JsonValidator(
+ '{"state": "ON", "color": {"x": 0.611, "y": 0.375},'
+ ' "white_value": 80}'),
+ 0, False),
+ ], any_order=True)
+
async def test_flash_short_and_long(hass, mqtt_mock):
"""Test for flash length being sent when included."""
@@ -335,7 +575,6 @@ async def test_flash_short_and_long(hass, mqtt_mock):
'platform': 'mqtt',
'schema': 'json',
'name': 'test',
- 'state_topic': 'test_light_rgb',
'command_topic': 'test_light_rgb/set',
'flash_time_short': 5,
'flash_time_long': 15,
@@ -347,6 +586,26 @@ async def test_flash_short_and_long(hass, mqtt_mock):
assert STATE_OFF == state.state
assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES)
+ common.async_turn_on(hass, 'light.test', flash='short')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'test_light_rgb/set', JsonValidator(
+ '{"state": "ON", "flash": 5}'), 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('light.test')
+ assert STATE_ON == state.state
+
+ common.async_turn_on(hass, 'light.test', flash='long')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'test_light_rgb/set', JsonValidator(
+ '{"state": "ON", "flash": 15}'), 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('light.test')
+ assert STATE_ON == state.state
+
async def test_transition(hass, mqtt_mock):
"""Test for transition time being sent when included."""
@@ -355,7 +614,6 @@ async def test_transition(hass, mqtt_mock):
'platform': 'mqtt',
'schema': 'json',
'name': 'test',
- 'state_topic': 'test_light_rgb',
'command_topic': 'test_light_rgb/set',
'qos': 0
}
@@ -365,6 +623,26 @@ async def test_transition(hass, mqtt_mock):
assert STATE_OFF == state.state
assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES)
+ common.async_turn_on(hass, 'light.test', transition=15)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'test_light_rgb/set', JsonValidator(
+ '{"state": "ON", "transition": 15}'), 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('light.test')
+ assert STATE_ON == state.state
+
+ common.async_turn_off(hass, 'light.test', transition=30)
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'test_light_rgb/set', JsonValidator(
+ '{"state": "OFF", "transition": 30}'), 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('light.test')
+ assert STATE_OFF == state.state
+
async def test_brightness_scale(hass, mqtt_mock):
"""Test for brightness scaling."""
diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py
index 52dd3ecfbdb..cc629b2165d 100644
--- a/tests/components/mqtt/test_lock.py
+++ b/tests/components/mqtt/test_lock.py
@@ -11,6 +11,7 @@ from homeassistant.setup import async_setup_component
from tests.common import (
MockConfigEntry, async_fire_mqtt_message, async_mock_mqtt_component,
mock_registry)
+from tests.components.lock import common
async def test_controlling_state_via_topic(hass, mqtt_mock):
@@ -75,6 +76,82 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock):
assert state.state is STATE_UNLOCKED
+async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
+ """Test optimistic mode without state topic."""
+ assert await async_setup_component(hass, lock.DOMAIN, {
+ lock.DOMAIN: {
+ 'platform': 'mqtt',
+ 'name': 'test',
+ 'command_topic': 'command-topic',
+ 'payload_lock': 'LOCK',
+ 'payload_unlock': 'UNLOCK'
+ }
+ })
+
+ state = hass.states.get('lock.test')
+ assert state.state is STATE_UNLOCKED
+ assert state.attributes.get(ATTR_ASSUMED_STATE)
+
+ common.async_lock(hass, 'lock.test')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'command-topic', 'LOCK', 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('lock.test')
+ assert state.state is STATE_LOCKED
+ assert state.attributes.get(ATTR_ASSUMED_STATE)
+
+ common.async_unlock(hass, 'lock.test')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'command-topic', 'UNLOCK', 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('lock.test')
+ assert state.state is STATE_UNLOCKED
+ assert state.attributes.get(ATTR_ASSUMED_STATE)
+
+
+async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock):
+ """Test optimistic mode without state topic."""
+ assert await async_setup_component(hass, lock.DOMAIN, {
+ lock.DOMAIN: {
+ 'platform': 'mqtt',
+ 'name': 'test',
+ 'state_topic': 'state-topic',
+ 'command_topic': 'command-topic',
+ 'payload_lock': 'LOCK',
+ 'payload_unlock': 'UNLOCK',
+ 'optimistic': True
+ }
+ })
+
+ state = hass.states.get('lock.test')
+ assert state.state is STATE_UNLOCKED
+ assert state.attributes.get(ATTR_ASSUMED_STATE)
+
+ common.async_lock(hass, 'lock.test')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'command-topic', 'LOCK', 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('lock.test')
+ assert state.state is STATE_LOCKED
+ assert state.attributes.get(ATTR_ASSUMED_STATE)
+
+ common.async_unlock(hass, 'lock.test')
+ await hass.async_block_till_done()
+
+ mqtt_mock.async_publish.assert_called_once_with(
+ 'command-topic', 'UNLOCK', 0, False)
+ mqtt_mock.async_publish.reset_mock()
+ state = hass.states.get('lock.test')
+ assert state.state is STATE_UNLOCKED
+ assert state.attributes.get(ATTR_ASSUMED_STATE)
+
+
async def test_default_availability_payload(hass, mqtt_mock):
"""Test availability by default payload with defined topic."""
assert await async_setup_component(hass, lock.DOMAIN, {
diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py
index 71ef1dc1e43..ba05459185d 100644
--- a/tests/components/mqtt/test_server.py
+++ b/tests/components/mqtt/test_server.py
@@ -36,9 +36,8 @@ class TestMQTT:
assert setup_component(self.hass, mqtt.DOMAIN, {
mqtt.DOMAIN: {CONF_PASSWORD: password},
})
+ self.hass.block_till_done()
assert mock_mqtt.called
- from pprint import pprint
- pprint(mock_mqtt.mock_calls)
assert mock_mqtt.mock_calls[1][2]['username'] == 'homeassistant'
assert mock_mqtt.mock_calls[1][2]['password'] == password
@@ -61,9 +60,8 @@ class TestMQTT:
'http': {'api_password': 'http_secret'},
mqtt.DOMAIN: {CONF_PASSWORD: password},
})
+ self.hass.block_till_done()
assert mock_mqtt.called
- from pprint import pprint
- pprint(mock_mqtt.mock_calls)
assert mock_mqtt.mock_calls[1][2]['username'] == 'homeassistant'
assert mock_mqtt.mock_calls[1][2]['password'] == password
diff --git a/tests/components/mqtt/test_vacuum.py b/tests/components/mqtt/test_vacuum.py
index 6a61495c143..78ca45a792f 100644
--- a/tests/components/mqtt/test_vacuum.py
+++ b/tests/components/mqtt/test_vacuum.py
@@ -1,6 +1,6 @@
"""The tests for the Mqtt vacuum platform."""
+import copy
import json
-
import pytest
from homeassistant.components import mqtt, vacuum
@@ -31,8 +31,8 @@ default_config = {
mqttvacuum.CONF_CLEANING_TEMPLATE: '{{ value_json.cleaning }}',
mqttvacuum.CONF_DOCKED_TOPIC: 'vacuum/state',
mqttvacuum.CONF_DOCKED_TEMPLATE: '{{ value_json.docked }}',
- mqttvacuum.CONF_STATE_TOPIC: 'vacuum/state',
- mqttvacuum.CONF_STATE_TEMPLATE: '{{ value_json.state }}',
+ mqttvacuum.CONF_ERROR_TOPIC: 'vacuum/state',
+ mqttvacuum.CONF_ERROR_TEMPLATE: '{{ value_json.error }}',
mqttvacuum.CONF_FAN_SPEED_TOPIC: 'vacuum/state',
mqttvacuum.CONF_FAN_SPEED_TEMPLATE: '{{ value_json.fan_speed }}',
mqttvacuum.CONF_SET_FAN_SPEED_TOPIC: 'vacuum/set_fan_speed',
@@ -130,6 +130,16 @@ async def test_all_commands(hass, mock_publish):
await hass.async_block_till_done()
mock_publish.async_publish.assert_called_once_with(
'vacuum/send_command', '44 FE 93', 0, False)
+ mock_publish.async_publish.reset_mock()
+
+ common.send_command(hass, '44 FE 93', {"key": "value"},
+ entity_id='vacuum.mqtttest')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ assert json.loads(mock_publish.async_publish.mock_calls[-1][1][1]) == {
+ "command": "44 FE 93",
+ "key": "value"
+ }
async def test_status(hass, mock_publish):
@@ -177,6 +187,129 @@ async def test_status(hass, mock_publish):
assert 'min' == state.attributes.get(ATTR_FAN_SPEED)
+async def test_status_battery(hass, mock_publish):
+ """Test status updates from the vacuum."""
+ default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
+ mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: default_config,
+ })
+
+ message = """{
+ "battery_level": 54
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert 'mdi:battery-50' == \
+ state.attributes.get(ATTR_BATTERY_ICON)
+
+
+async def test_status_cleaning(hass, mock_publish):
+ """Test status updates from the vacuum."""
+ default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
+ mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: default_config,
+ })
+
+ message = """{
+ "cleaning": true
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert STATE_ON == state.state
+
+
+async def test_status_docked(hass, mock_publish):
+ """Test status updates from the vacuum."""
+ default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
+ mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: default_config,
+ })
+
+ message = """{
+ "docked": true
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert STATE_OFF == state.state
+
+
+async def test_status_charging(hass, mock_publish):
+ """Test status updates from the vacuum."""
+ default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
+ mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: default_config,
+ })
+
+ message = """{
+ "charging": true
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert 'mdi:battery-outline' == \
+ state.attributes.get(ATTR_BATTERY_ICON)
+
+
+async def test_status_fan_speed(hass, mock_publish):
+ """Test status updates from the vacuum."""
+ default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
+ mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: default_config,
+ })
+
+ message = """{
+ "fan_speed": "max"
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert 'max' == state.attributes.get(ATTR_FAN_SPEED)
+
+
+async def test_status_error(hass, mock_publish):
+ """Test status updates from the vacuum."""
+ default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
+ mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: default_config,
+ })
+
+ message = """{
+ "error": "Error1"
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert 'Error: Error1' == state.attributes.get(ATTR_STATUS)
+
+ message = """{
+ "error": ""
+ }"""
+ async_fire_mqtt_message(hass, 'vacuum/state', message)
+ await hass.async_block_till_done()
+ state = hass.states.get('vacuum.mqtttest')
+ assert 'Stopped' == state.attributes.get(ATTR_STATUS)
+
+
async def test_battery_template(hass, mock_publish):
"""Test that you can use non-default templates for battery_level."""
default_config.update({
@@ -214,6 +347,84 @@ async def test_status_invalid_json(hass, mock_publish):
assert "Stopped" == state.attributes.get(ATTR_STATUS)
+async def test_missing_battery_template(hass, mock_publish):
+ """Test to make sure missing template is not allowed."""
+ config = copy.deepcopy(default_config)
+ config.pop(mqttvacuum.CONF_BATTERY_LEVEL_TEMPLATE)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: config,
+ })
+
+ state = hass.states.get('vacuum.mqtttest')
+ assert state is None
+
+
+async def test_missing_charging_template(hass, mock_publish):
+ """Test to make sure missing template is not allowed."""
+ config = copy.deepcopy(default_config)
+ config.pop(mqttvacuum.CONF_CHARGING_TEMPLATE)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: config,
+ })
+
+ state = hass.states.get('vacuum.mqtttest')
+ assert state is None
+
+
+async def test_missing_cleaning_template(hass, mock_publish):
+ """Test to make sure missing template is not allowed."""
+ config = copy.deepcopy(default_config)
+ config.pop(mqttvacuum.CONF_CLEANING_TEMPLATE)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: config,
+ })
+
+ state = hass.states.get('vacuum.mqtttest')
+ assert state is None
+
+
+async def test_missing_docked_template(hass, mock_publish):
+ """Test to make sure missing template is not allowed."""
+ config = copy.deepcopy(default_config)
+ config.pop(mqttvacuum.CONF_DOCKED_TEMPLATE)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: config,
+ })
+
+ state = hass.states.get('vacuum.mqtttest')
+ assert state is None
+
+
+async def test_missing_error_template(hass, mock_publish):
+ """Test to make sure missing template is not allowed."""
+ config = copy.deepcopy(default_config)
+ config.pop(mqttvacuum.CONF_ERROR_TEMPLATE)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: config,
+ })
+
+ state = hass.states.get('vacuum.mqtttest')
+ assert state is None
+
+
+async def test_missing_fan_speed_template(hass, mock_publish):
+ """Test to make sure missing template is not allowed."""
+ config = copy.deepcopy(default_config)
+ config.pop(mqttvacuum.CONF_FAN_SPEED_TEMPLATE)
+
+ assert await async_setup_component(hass, vacuum.DOMAIN, {
+ vacuum.DOMAIN: config,
+ })
+
+ state = hass.states.get('vacuum.mqtttest')
+ assert state is None
+
+
async def test_default_availability_payload(hass, mock_publish):
"""Test availability by default payload with defined topic."""
default_config.update({
diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py
index 53516885f30..ae7b70e7fe6 100644
--- a/tests/components/nx584/test_binary_sensor.py
+++ b/tests/components/nx584/test_binary_sensor.py
@@ -85,7 +85,7 @@ class TestNX584SensorSetup(unittest.TestCase):
def _test_assert_graceful_fail(self, config):
"""Test the failing."""
assert not setup_component(
- self.hass, 'binary_sensor.nx584', config)
+ self.hass, 'nx584', config)
def test_setup_bad_config(self):
"""Test the setup with bad configuration."""
diff --git a/tests/components/panel_custom/test_init.py b/tests/components/panel_custom/test_init.py
index 8c95f96085a..b93a97eee4c 100644
--- a/tests/components/panel_custom/test_init.py
+++ b/tests/components/panel_custom/test_init.py
@@ -25,7 +25,11 @@ async def test_webcomponent_custom_path_not_found(hass):
hass, 'panel_custom', config
)
assert not result
- assert len(hass.data.get(frontend.DATA_PANELS, {})) == 0
+
+ panels = hass.data.get(frontend.DATA_PANELS, [])
+
+ assert panels
+ assert 'nice_url' not in panels
async def test_webcomponent_custom_path(hass):
diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py
index ef129a555be..cde7633b1a3 100644
--- a/tests/components/person/test_init.py
+++ b/tests/components/person/test_init.py
@@ -1,24 +1,26 @@
"""The tests for the person component."""
from unittest.mock import Mock
+import pytest
+
+from homeassistant.components.device_tracker import (
+ ATTR_SOURCE_TYPE, SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER)
from homeassistant.components.person import (
ATTR_SOURCE, ATTR_USER_ID, DOMAIN, PersonManager)
from homeassistant.const import (
- ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_GPS_ACCURACY,
- STATE_UNKNOWN, EVENT_HOMEASSISTANT_START)
-from homeassistant.components.device_tracker import (
- ATTR_SOURCE_TYPE, SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER)
+ ATTR_GPS_ACCURACY, ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE,
+ EVENT_HOMEASSISTANT_START, STATE_UNKNOWN)
from homeassistant.core import CoreState, State
from homeassistant.setup import async_setup_component
-import pytest
-
-from tests.common import mock_component, mock_restore_cache, mock_coro_func
+from tests.common import (
+ assert_setup_component, mock_component, mock_coro_func, mock_restore_cache)
DEVICE_TRACKER = 'device_tracker.test_tracker'
DEVICE_TRACKER_2 = 'device_tracker.test_tracker_2'
+# pylint: disable=redefined-outer-name
@pytest.fixture
def storage_setup(hass, hass_storage, hass_admin_user):
"""Storage setup."""
@@ -44,7 +46,8 @@ def storage_setup(hass, hass_storage, hass_admin_user):
async def test_minimal_setup(hass):
"""Test minimal config with only name."""
config = {DOMAIN: {'id': '1234', 'name': 'test person'}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(1):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.test_person')
assert state.state == STATE_UNKNOWN
@@ -71,7 +74,8 @@ async def test_setup_user_id(hass, hass_admin_user):
user_id = hass_admin_user.id
config = {
DOMAIN: {'id': '1234', 'name': 'test person', 'user_id': user_id}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(1):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.test_person')
assert state.state == STATE_UNKNOWN
@@ -88,7 +92,8 @@ async def test_valid_invalid_user_ids(hass, hass_admin_user):
config = {DOMAIN: [
{'id': '1234', 'name': 'test valid user', 'user_id': user_id},
{'id': '5678', 'name': 'test bad user', 'user_id': 'bad_user_id'}]}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(2):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.test_valid_user')
assert state.state == STATE_UNKNOWN
@@ -108,7 +113,8 @@ async def test_setup_tracker(hass, hass_admin_user):
config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id,
'device_trackers': DEVICE_TRACKER}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(1):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.tracked_person')
assert state.state == STATE_UNKNOWN
@@ -159,7 +165,8 @@ async def test_setup_two_trackers(hass, hass_admin_user):
config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id,
'device_trackers': [DEVICE_TRACKER, DEVICE_TRACKER_2]}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(1):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.tracked_person')
assert state.state == STATE_UNKNOWN
@@ -231,7 +238,8 @@ async def test_ignore_unavailable_states(hass, hass_admin_user):
config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id,
'device_trackers': [DEVICE_TRACKER, DEVICE_TRACKER_2]}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(1):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.tracked_person')
assert state.state == STATE_UNKNOWN
@@ -275,7 +283,8 @@ async def test_restore_home_state(hass, hass_admin_user):
config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id,
'device_trackers': DEVICE_TRACKER}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(1):
+ assert await async_setup_component(hass, DOMAIN, config)
state = hass.states.get('person.tracked_person')
assert state.state == 'home'
@@ -292,7 +301,8 @@ async def test_duplicate_ids(hass, hass_admin_user):
config = {DOMAIN: [
{'id': '1234', 'name': 'test user 1'},
{'id': '1234', 'name': 'test user 2'}]}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(2):
+ assert await async_setup_component(hass, DOMAIN, config)
assert len(hass.states.async_entity_ids('person')) == 1
assert hass.states.get('person.test_user_1') is not None
@@ -302,7 +312,8 @@ async def test_duplicate_ids(hass, hass_admin_user):
async def test_create_person_during_run(hass):
"""Test that person is updated if created while hass is running."""
config = {DOMAIN: {}}
- assert await async_setup_component(hass, DOMAIN, config)
+ with assert_setup_component(0):
+ assert await async_setup_component(hass, DOMAIN, config)
hass.states.async_set(DEVICE_TRACKER, 'home')
await hass.async_block_till_done()
diff --git a/tests/components/qwikswitch/test_init.py b/tests/components/qwikswitch/test_init.py
index 76655f32816..d6ad0607d42 100644
--- a/tests/components/qwikswitch/test_init.py
+++ b/tests/components/qwikswitch/test_init.py
@@ -7,6 +7,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH
from homeassistant.bootstrap import async_setup_component
from tests.test_util.aiohttp import mock_aiohttp_client
+from aiohttp.client_exceptions import ClientError
_LOGGER = logging.getLogger(__name__)
@@ -23,6 +24,8 @@ class AiohttpClientMockResponseList(list):
try:
res = list.pop(self, 0)
_LOGGER.debug("MockResponseList popped %s: %s", res, self)
+ if isinstance(res, Exception):
+ raise res
return res
except IndexError:
raise AssertionError("MockResponseList empty")
@@ -54,7 +57,7 @@ def aioclient_mock():
yield mock_session
-async def test_binary_sensor_device(hass, aioclient_mock):
+async def test_binary_sensor_device(hass, aioclient_mock): # noqa
"""Test a binary sensor device."""
config = {
'qwikswitch': {
@@ -75,7 +78,8 @@ async def test_binary_sensor_device(hass, aioclient_mock):
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
LISTEN.append('{"id":"@a00001","cmd":"","data":"4e0e1601","rssi":"61%"}')
- LISTEN.append('') # Will cause a sleep
+ LISTEN.append(ClientError()) # Will cause a sleep
+
await hass.async_block_till_done()
state_obj = hass.states.get('binary_sensor.s1')
assert state_obj.state == 'on'
@@ -87,7 +91,7 @@ async def test_binary_sensor_device(hass, aioclient_mock):
assert state_obj.state == 'off'
-async def test_sensor_device(hass, aioclient_mock):
+async def test_sensor_device(hass, aioclient_mock): # noqa
"""Test a sensor device."""
config = {
'qwikswitch': {
@@ -100,8 +104,8 @@ async def test_sensor_device(hass, aioclient_mock):
}
}
await async_setup_component(hass, QWIKSWITCH, config)
- await hass.async_block_till_done()
+ await hass.async_block_till_done()
state_obj = hass.states.get('sensor.ss1')
assert state_obj.state == 'None'
@@ -110,8 +114,7 @@ async def test_sensor_device(hass, aioclient_mock):
LISTEN.append(
'{"id":"@a00001","name":"ss1","type":"rel",'
'"val":"4733800001a00000"}')
- LISTEN.append('') # Will cause a sleep
- await LISTEN.wait_till_empty(hass) # await hass.async_block_till_done()
+ await hass.async_block_till_done()
state_obj = hass.states.get('sensor.ss1')
- assert state_obj.state == 'None'
+ assert state_obj.state == '416'
diff --git a/tests/components/rflink/test_init.py b/tests/components/rflink/test_init.py
index 46cbef92aa4..69d5049902b 100644
--- a/tests/components/rflink/test_init.py
+++ b/tests/components/rflink/test_init.py
@@ -42,7 +42,9 @@ async def mock_rflink(hass, config, domain, monkeypatch, failures=None,
'rflink.protocol.create_rflink_connection',
mock_create)
+ await async_setup_component(hass, 'rflink', config)
await async_setup_component(hass, domain, config)
+ await hass.async_block_till_done()
# hook into mock config for injecting events
event_callback = mock_create.call_args_list[0][1]['event_callback']
diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py
index df96b19f351..940999c2dbe 100644
--- a/tests/components/scene/test_init.py
+++ b/tests/components/scene/test_init.py
@@ -3,7 +3,6 @@ import io
import unittest
from homeassistant.setup import setup_component
-from homeassistant import loader
from homeassistant.components import light, scene
from homeassistant.util import yaml
@@ -18,7 +17,7 @@ class TestScene(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
- test_light = loader.get_component(self.hass, 'light.test')
+ test_light = getattr(self.hass.components, 'test.light')
test_light.init()
assert setup_component(self.hass, light.DOMAIN, {
diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py
index 580b11f5ccd..c558c476ca1 100644
--- a/tests/components/statistics/test_sensor.py
+++ b/tests/components/statistics/test_sensor.py
@@ -230,7 +230,7 @@ class TestStatisticsSensor(unittest.TestCase):
state.attributes.get('max_age')
assert self.change_rate == state.attributes.get('change_rate')
- @pytest.mark.skip
+ @pytest.mark.skip("Flaky in CI")
def test_initialize_from_database(self):
"""Test initializing the statistics from the database."""
# enable the recorder
@@ -260,6 +260,7 @@ class TestStatisticsSensor(unittest.TestCase):
state = self.hass.states.get('sensor.test_mean')
assert str(self.mean) == state.state
+ @pytest.mark.skip("Flaky in CI")
def test_initialize_from_database_with_maxage(self):
"""Test initializing the statistics from the database."""
mock_data = {
diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py
index a2c962ffb45..9d898d96d78 100644
--- a/tests/components/stream/test_hls.py
+++ b/tests/components/stream/test_hls.py
@@ -110,7 +110,7 @@ async def test_stream_ended(hass):
while await track.recv() is not None:
segments += 1
- assert segments == 3
+ assert segments > 1
assert not track.get_segment()
# Stop stream, if it hasn't quit already
diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py
index 4e227e463b4..8e4a69e28ff 100644
--- a/tests/components/stream/test_recorder.py
+++ b/tests/components/stream/test_recorder.py
@@ -41,7 +41,7 @@ async def test_record_stream(hass, hass_client):
stream.stop()
- assert segments == 3
+ assert segments > 1
async def test_recorder_timeout(hass, hass_client):
diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py
index d39c5a24ddc..c951e3113b3 100644
--- a/tests/components/switch/test_init.py
+++ b/tests/components/switch/test_init.py
@@ -3,11 +3,11 @@
import unittest
from homeassistant.setup import setup_component, async_setup_component
-from homeassistant import core, loader
+from homeassistant import core
from homeassistant.components import switch
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
-from tests.common import get_test_home_assistant
+from tests.common import get_test_home_assistant, mock_entity_platform
from tests.components.switch import common
@@ -18,7 +18,7 @@ class TestSwitch(unittest.TestCase):
def setUp(self):
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
- platform = loader.get_component(self.hass, 'switch.test')
+ platform = getattr(self.hass.components, 'test.switch')
platform.init()
# Switch 1 is ON, switch 2 is OFF
self.switch_1, self.switch_2, self.switch_3 = \
@@ -77,10 +77,10 @@ class TestSwitch(unittest.TestCase):
def test_setup_two_platforms(self):
"""Test with bad configuration."""
# Test if switch component returns 0 switches
- test_platform = loader.get_component(self.hass, 'switch.test')
+ test_platform = getattr(self.hass.components, 'test.switch')
test_platform.init(True)
- loader.set_component(self.hass, 'switch.test2', test_platform)
+ mock_entity_platform(self.hass, 'switch.test2', test_platform)
test_platform.init(False)
assert setup_component(
@@ -99,6 +99,8 @@ async def test_switch_context(hass, hass_admin_user):
}
})
+ await hass.async_block_till_done()
+
state = hass.states.get('switch.ac')
assert state is not None
diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py
index 800c7b72ee6..4c2ad9d57c9 100644
--- a/tests/components/tradfri/test_init.py
+++ b/tests/components/tradfri/test_init.py
@@ -21,6 +21,7 @@ async def test_config_yaml_host_not_imported(hass):
'host': 'mock-host'
}
})
+ await hass.async_block_till_done()
assert len(mock_init.mock_calls) == 0
@@ -34,6 +35,7 @@ async def test_config_yaml_host_imported(hass):
'host': 'mock-host'
}
})
+ await hass.async_block_till_done()
progress = hass.config_entries.flow.async_progress()
assert len(progress) == 1
@@ -54,6 +56,7 @@ async def test_config_json_host_not_imported(hass):
assert await async_setup_component(hass, 'tradfri', {
'tradfri': {}
})
+ await hass.async_block_till_done()
assert len(mock_init.mock_calls) == 0
@@ -65,6 +68,7 @@ async def test_config_json_host_imported(hass, mock_gateway_info):
assert await async_setup_component(hass, 'tradfri', {
'tradfri': {}
})
+ await hass.async_block_till_done()
progress = hass.config_entries.flow.async_progress()
assert len(progress) == 1
diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py
index b77f9060b40..2116382eafe 100644
--- a/tests/components/trend/test_binary_sensor.py
+++ b/tests/components/trend/test_binary_sensor.py
@@ -1,5 +1,9 @@
"""The test for the Trend sensor platform."""
+from datetime import timedelta
+from unittest.mock import patch
+
from homeassistant import setup
+import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant, assert_setup_component
@@ -46,7 +50,7 @@ class TestTrendBinarySensor:
'sensors': {
'test_trend_sensor': {
'entity_id': "sensor.test_state",
- 'sample_duration': 300,
+ 'sample_duration': 10000,
'min_gradient': 1,
'max_samples': 25,
}
@@ -54,16 +58,22 @@ class TestTrendBinarySensor:
}
})
- for val in [1, 0, 2, 3]:
- self.hass.states.set('sensor.test_state', val)
+ now = dt_util.utcnow()
+ for val in [10, 0, 20, 30]:
+ with patch('homeassistant.util.dt.utcnow', return_value=now):
+ self.hass.states.set('sensor.test_state', val)
self.hass.block_till_done()
+ now += timedelta(seconds=2)
state = self.hass.states.get('binary_sensor.test_trend_sensor')
assert state.state == 'on'
- for val in [0, 1, 0, 0]:
- self.hass.states.set('sensor.test_state', val)
+ # have to change state value, otherwise sample will lost
+ for val in [0, 30, 1, 0]:
+ with patch('homeassistant.util.dt.utcnow', return_value=now):
+ self.hass.states.set('sensor.test_state', val)
self.hass.block_till_done()
+ now += timedelta(seconds=2)
state = self.hass.states.get('binary_sensor.test_trend_sensor')
assert state.state == 'off'
@@ -76,7 +86,7 @@ class TestTrendBinarySensor:
'sensors': {
'test_trend_sensor': {
'entity_id': "sensor.test_state",
- 'sample_duration': 300,
+ 'sample_duration': 10000,
'min_gradient': 1,
'max_samples': 25,
'invert': 'Yes'
@@ -85,16 +95,21 @@ class TestTrendBinarySensor:
}
})
- for val in [3, 2, 3, 1]:
- self.hass.states.set('sensor.test_state', val)
+ now = dt_util.utcnow()
+ for val in [30, 20, 30, 10]:
+ with patch('homeassistant.util.dt.utcnow', return_value=now):
+ self.hass.states.set('sensor.test_state', val)
self.hass.block_till_done()
+ now += timedelta(seconds=2)
state = self.hass.states.get('binary_sensor.test_trend_sensor')
assert state.state == 'on'
- for val in [4, 2, 4, 4]:
- self.hass.states.set('sensor.test_state', val)
+ for val in [30, 0, 45, 50]:
+ with patch('homeassistant.util.dt.utcnow', return_value=now):
+ self.hass.states.set('sensor.test_state', val)
self.hass.block_till_done()
+ now += timedelta(seconds=2)
state = self.hass.states.get('binary_sensor.test_trend_sensor')
assert state.state == 'off'
diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py
index 66011f3e6ee..7fc9f55cf03 100644
--- a/tests/components/zwave/test_init.py
+++ b/tests/components/zwave/test_init.py
@@ -3,6 +3,7 @@ import asyncio
from collections import OrderedDict
from datetime import datetime
from pytz import utc
+import voluptuous as vol
import unittest
from unittest.mock import patch, MagicMock
@@ -83,6 +84,35 @@ async def test_network_options(hass, mock_openzwave):
assert network.options.config_path == 'mock_config_path'
+async def test_network_key_validation(hass, mock_openzwave):
+ """Test network key validation."""
+ test_values = [
+ ('0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, '
+ '0x0C, 0x0D, 0x0E, 0x0F, 0x10'),
+ ('0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,'
+ '0x0E,0x0F,0x10'),
+ ]
+ for value in test_values:
+ result = zwave.CONFIG_SCHEMA({'zwave': {'network_key': value}})
+ assert result['zwave']['network_key'] == value
+
+
+async def test_erronous_network_key_fails_validation(hass, mock_openzwave):
+ """Test failing erronous network key validation."""
+ test_values = [
+ ('0x 01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, '
+ '0x0C, 0x0D, 0x0E, 0x0F, 0x10'),
+ ('0X01,0X02,0X03,0X04,0X05,0X06,0X07,0X08,0X09,0X0A,0X0B,0X0C,0X0D,'
+ '0X0E,0X0F,0X10'),
+ 'invalid',
+ '1234567',
+ 1234567
+ ]
+ for value in test_values:
+ with pytest.raises(vol.Invalid):
+ zwave.CONFIG_SCHEMA({'zwave': {'network_key': value}})
+
+
async def test_auto_heal_midnight(hass, mock_openzwave):
"""Test network auto-heal at midnight."""
await async_setup_component(hass, 'zwave', {
@@ -528,13 +558,13 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
"""Stop everything that was started."""
self.hass.stop()
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_discovery(self, discovery, get_platform):
+ def test_entity_discovery(self, discovery, import_module):
"""Test the creation of a new entity."""
discovery.async_load_platform.return_value = mock_coro()
mock_platform = MagicMock()
- get_platform.return_value = mock_platform
+ import_module.return_value = mock_platform
mock_device = MagicMock()
mock_device.name = 'test_device'
mock_platform.get_device.return_value = mock_device
@@ -588,13 +618,13 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert values._entity.value_changed.called
assert len(values._entity.value_changed.mock_calls) == 1
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_existing_values(self, discovery, get_platform):
+ def test_entity_existing_values(self, discovery, import_module):
"""Test the loading of already discovered values."""
discovery.async_load_platform.return_value = mock_coro()
mock_platform = MagicMock()
- get_platform.return_value = mock_platform
+ import_module.return_value = mock_platform
mock_device = MagicMock()
mock_device.name = 'test_device'
mock_platform.get_device.return_value = mock_device
@@ -633,9 +663,9 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert args[4] == self.zwave_config
assert not self.primary.enable_poll.called
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_node_schema_mismatch(self, discovery, get_platform):
+ def test_node_schema_mismatch(self, discovery, import_module):
"""Test node schema mismatch."""
self.node.generic = 'no_match'
self.node.values = {
@@ -656,13 +686,13 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert not discovery.async_load_platform.called
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_workaround_component(self, discovery, get_platform):
+ def test_entity_workaround_component(self, discovery, import_module):
"""Test component workaround."""
discovery.async_load_platform.return_value = mock_coro()
mock_platform = MagicMock()
- get_platform.return_value = mock_platform
+ import_module.return_value = mock_platform
mock_device = MagicMock()
mock_device.name = 'test_device'
mock_platform.get_device.return_value = mock_device
@@ -699,9 +729,9 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
args = mock_dispatch_send.mock_calls[0][1]
assert args[1] == 'zwave_new_binary_sensor'
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_workaround_ignore(self, discovery, get_platform):
+ def test_entity_workaround_ignore(self, discovery, import_module):
"""Test ignore workaround."""
self.node.manufacturer_id = '010f'
self.node.product_type = '0301'
@@ -728,9 +758,9 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert not discovery.async_load_platform.called
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_config_ignore(self, discovery, get_platform):
+ def test_entity_config_ignore(self, discovery, import_module):
"""Test ignore config."""
self.node.values = {
self.primary.value_id: self.primary,
@@ -752,9 +782,10 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert not discovery.async_load_platform.called
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_config_ignore_with_registry(self, discovery, get_platform):
+ def test_entity_config_ignore_with_registry(self, discovery,
+ import_module):
"""Test ignore config.
The case when the device is in entity registry.
@@ -783,16 +814,16 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert not discovery.async_load_platform.called
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_entity_platform_ignore(self, discovery, get_platform):
+ def test_entity_platform_ignore(self, discovery, import_module):
"""Test platform ignore device."""
self.node.values = {
self.primary.value_id: self.primary,
self.secondary.value_id: self.secondary,
}
platform = MagicMock()
- get_platform.return_value = platform
+ import_module.return_value = platform
platform.get_device.return_value = None
zwave.ZWaveDeviceEntityValues(
hass=self.hass,
@@ -806,12 +837,12 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
assert not discovery.async_load_platform.called
- @patch.object(zwave, 'get_platform')
+ @patch.object(zwave, 'import_module')
@patch.object(zwave, 'discovery')
- def test_config_polling_intensity(self, discovery, get_platform):
+ def test_config_polling_intensity(self, discovery, import_module):
"""Test polling intensity."""
mock_platform = MagicMock()
- get_platform.return_value = mock_platform
+ import_module.return_value = mock_platform
mock_device = MagicMock()
mock_device.name = 'test_device'
mock_platform.get_device.return_value = mock_device
@@ -1083,7 +1114,7 @@ class TestZWaveServices(unittest.TestCase):
def test_set_config_parameter(self):
"""Test zwave set_config_parameter service."""
- value = MockValue(
+ value_byte = MockValue(
index=12,
command_class=const.COMMAND_CLASS_CONFIGURATION,
type=const.TYPE_BYTE,
@@ -1094,23 +1125,43 @@ class TestZWaveServices(unittest.TestCase):
type=const.TYPE_LIST,
data_items=['item1', 'item2', 'item3'],
)
+ value_button = MockValue(
+ index=14,
+ command_class=const.COMMAND_CLASS_CONFIGURATION,
+ type=const.TYPE_BUTTON,
+ )
value_list_int = MockValue(
index=15,
command_class=const.COMMAND_CLASS_CONFIGURATION,
type=const.TYPE_LIST,
data_items=['1', '2', '3'],
)
- value_button = MockValue(
- index=14,
+ value_bool = MockValue(
+ index=16,
command_class=const.COMMAND_CLASS_CONFIGURATION,
- type=const.TYPE_BUTTON,
+ type=const.TYPE_BOOL,
)
node = MockNode(node_id=14)
- node.get_values.return_value = {12: value, 13: value_list,
- 14: value_button,
- 15: value_list_int}
+ node.get_values.return_value = {
+ 12: value_byte,
+ 13: value_list,
+ 14: value_button,
+ 15: value_list_int,
+ 16: value_bool
+ }
self.zwave_network.nodes = {14: node}
+ # Byte
+ self.hass.services.call('zwave', 'set_config_parameter', {
+ const.ATTR_NODE_ID: 14,
+ const.ATTR_CONFIG_PARAMETER: 12,
+ const.ATTR_CONFIG_VALUE: 7,
+ })
+ self.hass.block_till_done()
+
+ assert value_byte.data == 7
+
+ # List
self.hass.services.call('zwave', 'set_config_parameter', {
const.ATTR_NODE_ID: 14,
const.ATTR_CONFIG_PARAMETER: 13,
@@ -1120,24 +1171,7 @@ class TestZWaveServices(unittest.TestCase):
assert value_list.data == 'item3'
- self.hass.services.call('zwave', 'set_config_parameter', {
- const.ATTR_NODE_ID: 14,
- const.ATTR_CONFIG_PARAMETER: 15,
- const.ATTR_CONFIG_VALUE: 3,
- })
- self.hass.block_till_done()
-
- assert value_list_int.data == '3'
-
- self.hass.services.call('zwave', 'set_config_parameter', {
- const.ATTR_NODE_ID: 14,
- const.ATTR_CONFIG_PARAMETER: 12,
- const.ATTR_CONFIG_VALUE: 7,
- })
- self.hass.block_till_done()
-
- assert value.data == 7
-
+ # Button
self.hass.services.call('zwave', 'set_config_parameter', {
const.ATTR_NODE_ID: 14,
const.ATTR_CONFIG_PARAMETER: 14,
@@ -1148,6 +1182,37 @@ class TestZWaveServices(unittest.TestCase):
assert self.zwave_network.manager.pressButton.called
assert self.zwave_network.manager.releaseButton.called
+ # List of Ints
+ self.hass.services.call('zwave', 'set_config_parameter', {
+ const.ATTR_NODE_ID: 14,
+ const.ATTR_CONFIG_PARAMETER: 15,
+ const.ATTR_CONFIG_VALUE: 3,
+ })
+ self.hass.block_till_done()
+
+ assert value_list_int.data == '3'
+
+ # Boolean Truthy
+ self.hass.services.call('zwave', 'set_config_parameter', {
+ const.ATTR_NODE_ID: 14,
+ const.ATTR_CONFIG_PARAMETER: 16,
+ const.ATTR_CONFIG_VALUE: 'True',
+ })
+ self.hass.block_till_done()
+
+ assert value_bool.data == 1
+
+ # Boolean Falsy
+ self.hass.services.call('zwave', 'set_config_parameter', {
+ const.ATTR_NODE_ID: 14,
+ const.ATTR_CONFIG_PARAMETER: 16,
+ const.ATTR_CONFIG_VALUE: 'False',
+ })
+ self.hass.block_till_done()
+
+ assert value_bool.data == 0
+
+ # Different Parameter Size
self.hass.services.call('zwave', 'set_config_parameter', {
const.ATTR_NODE_ID: 14,
const.ATTR_CONFIG_PARAMETER: 19,
diff --git a/tests/fixtures/homekit_controller/aqara_gateway.json b/tests/fixtures/homekit_controller/aqara_gateway.json
new file mode 100644
index 00000000000..092936f3da5
--- /dev/null
+++ b/tests/fixtures/homekit_controller/aqara_gateway.json
@@ -0,0 +1,488 @@
+[
+ {
+ "services": [
+ {
+ "iid": 1,
+ "characteristics": [
+ {
+ "value": "Aqara",
+ "description": "Manufacturer",
+ "type": "20",
+ "iid": 3,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "ZHWA11LM",
+ "description": "Model",
+ "type": "21",
+ "iid": 4,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "Aqara Hub-1563",
+ "description": "Name",
+ "type": "23",
+ "iid": 5,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "0000000123456789",
+ "description": "Serial Number",
+ "type": "30",
+ "iid": 6,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "description": "Identify",
+ "iid": 7,
+ "perms": [
+ "pw"
+ ],
+ "type": "14",
+ "format": "bool"
+ },
+ {
+ "value": "1.4.7",
+ "description": "Firmware Revision",
+ "type": "52",
+ "iid": 8,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ }
+ ],
+ "type": "3e"
+ },
+ {
+ "iid": 60,
+ "characteristics": [
+ {
+ "value": "1.1.0",
+ "description": "Protocol Version",
+ "type": "37",
+ "iid": 62,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ }
+ ],
+ "type": "a2"
+ },
+ {
+ "hidden": true,
+ "iid": 65536,
+ "characteristics": [
+ {
+ "value": false,
+ "description": "New Accessory Permission",
+ "type": "b1c09e4c-e202-4827-b343-b0f32f727cff",
+ "iid": 65538,
+ "perms": [
+ "pr",
+ "pw",
+ "ev",
+ "hd"
+ ],
+ "format": "bool"
+ },
+ {
+ "value": "()",
+ "description": "Accessory Joined",
+ "type": "2cb22739-1e4c-4798-a712-bc2faf51afc3",
+ "maxLen": 256,
+ "iid": 65539,
+ "perms": [
+ "pr",
+ "ev",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": " ",
+ "description": "Remove Accessory",
+ "type": "75d19fa9-218b-4943-427e-341e5d1c60cc",
+ "iid": 65540,
+ "perms": [
+ "pr",
+ "pw",
+ "ev",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "maxValue": 100,
+ "value": 40,
+ "minValue": 0,
+ "description": "Gateway Volume",
+ "type": "ee56b186-b0d3-528e-8c79-c21fc9bcf437",
+ "unit": "percentage",
+ "iid": 65541,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw"
+ ],
+ "format": "int"
+ },
+ {
+ "value": "Chinese",
+ "description": "Language",
+ "type": "4cf1436a-755c-1277-bdb8-30be29eb8620",
+ "iid": 65542,
+ "perms": [
+ "pr",
+ "pw"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "2019-02-12 06:45:07+10",
+ "description": "Date and Time",
+ "type": "4cb28907-66df-4d9c-924c-9971abf30edc",
+ "iid": 65543,
+ "perms": [
+ "pr",
+ "pw"
+ ],
+ "format": "string"
+ },
+ {
+ "value": " ",
+ "description": "Identify Accessory",
+ "type": "e1c20b22-e3a7-4b12-8ba3-c16e778648a7",
+ "iid": 65544,
+ "perms": [
+ "pr",
+ "ev",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "aiot-coap.aqara.cn",
+ "description": "Country Domain",
+ "type": "25d889cb-7135-4a21-b5b4-c1ffd6d2dd5c",
+ "iid": 65545,
+ "perms": [
+ "pr",
+ "pw",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": -1,
+ "description": "Firmware Update Status",
+ "type": "7d943f6a-e052-4e96-a124-d17bf00e32cb",
+ "iid": 65546,
+ "perms": [
+ "pr",
+ "ev",
+ "hd"
+ ],
+ "format": "int"
+ },
+ {
+ "description": "Firmware Update Data",
+ "iid": 65547,
+ "perms": [
+ "pw",
+ "hd"
+ ],
+ "type": "7f51dc43-dc68-4237-bae8-d705e61139f5",
+ "format": "data"
+ },
+ {
+ "description": "Firmware Update URL",
+ "type": "a45efd52-0db5-4c1a-1227-513fbcd8185f",
+ "maxLen": 256,
+ "iid": 65548,
+ "perms": [
+ "pw",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "description": "Firmware Update Checksum",
+ "iid": 65549,
+ "perms": [
+ "pw",
+ "hd"
+ ],
+ "type": "40f0124a-579d-40e4-245e-0ef6740ea64b",
+ "format": "string"
+ }
+ ],
+ "type": "9715bf53-ab63-4449-8dc7-2485d617390a"
+ },
+ {
+ "iid": 65792,
+ "characteristics": [
+ {
+ "value": "Lightbulb-1563",
+ "description": "Name",
+ "type": "23",
+ "iid": 65794,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "value": false,
+ "description": "On",
+ "type": "25",
+ "iid": 65795,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "format": "bool"
+ },
+ {
+ "maxValue": 360,
+ "value": 0,
+ "minValue": 0,
+ "description": "Hue",
+ "type": "13",
+ "unit": "arcdegrees",
+ "iid": 65796,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "format": "float"
+ },
+ {
+ "maxValue": 100,
+ "value": 100,
+ "minValue": 0,
+ "description": "Saturation",
+ "type": "2f",
+ "unit": "percentage",
+ "iid": 65797,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "format": "float"
+ },
+ {
+ "maxValue": 100,
+ "value": 0,
+ "minValue": 0,
+ "description": "Brightness",
+ "type": "8",
+ "unit": "percentage",
+ "iid": 65798,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "format": "int"
+ },
+ {
+ "value": "",
+ "description": "Timers",
+ "type": "232aa6bd-6ce2-4d7f-b7cf-52305f0d2bcf",
+ "iid": 65799,
+ "perms": [
+ "pr",
+ "pw",
+ "hd"
+ ],
+ "format": "tlv8"
+ }
+ ],
+ "type": "43"
+ },
+ {
+ "iid": 66048,
+ "characteristics": [
+ {
+ "value": "MIIO Service",
+ "description": "Name",
+ "type": "23",
+ "iid": 66050,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "value": false,
+ "description": "miio provisioned",
+ "type": "6ef066c1-08f8-46de-9121-b89b77e459e7",
+ "iid": 66051,
+ "perms": [
+ "pr",
+ "hd"
+ ],
+ "format": "bool"
+ },
+ {
+ "description": "miio bindkey",
+ "iid": 66052,
+ "perms": [
+ "pw",
+ "hd"
+ ],
+ "type": "6ef066c2-08f8-46de-9121-b89b77e459e7",
+ "format": "string"
+ },
+ {
+ "value": "152601563",
+ "description": "miio did",
+ "type": "6ef066c5-08f8-46de-9121-b89b77e459e7",
+ "iid": 66053,
+ "perms": [
+ "pr",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "lumi.gateway.aqhm01",
+ "description": "miio model",
+ "type": "6ef066c4-08f8-46de-9121-b89b77e459e7",
+ "iid": 66054,
+ "perms": [
+ "pr",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "ch",
+ "description": "miio country domain",
+ "type": "6ef066c3-08f8-46de-9121-b89b77e459e7",
+ "iid": 66055,
+ "perms": [
+ "pr",
+ "pw",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "country code",
+ "description": "miio country code",
+ "type": "6ef066d1-08f8-46de-9121-b89b77e459e7",
+ "iid": 66056,
+ "perms": [
+ "pr",
+ "pw",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": "app",
+ "description": "miio config type",
+ "type": "6ef066d3-08f8-46de-9121-b89b77e459e7",
+ "iid": 66057,
+ "perms": [
+ "pr",
+ "pw",
+ "hd"
+ ],
+ "format": "string"
+ },
+ {
+ "value": 28800,
+ "description": "miio gmt offset",
+ "type": "6ef066d2-08f8-46de-9121-b89b77e459e7",
+ "unit": "seconds",
+ "iid": 66058,
+ "perms": [
+ "pr",
+ "pw",
+ "hd"
+ ],
+ "format": "int"
+ }
+ ],
+ "type": "6ef066c0-08f8-46de-9121-b89b77e459e7"
+ },
+ {
+ "iid": 66304,
+ "characteristics": [
+ {
+ "value": "Security System",
+ "description": "Name",
+ "type": "23",
+ "iid": 66306,
+ "perms": [
+ "pr"
+ ],
+ "format": "string"
+ },
+ {
+ "maxValue": 4,
+ "value": 3,
+ "minValue": 0,
+ "description": "Security System Current State",
+ "type": "66",
+ "valid-values": [
+ 1,
+ 3,
+ 4
+ ],
+ "iid": 66307,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "format": "uint8"
+ },
+ {
+ "maxValue": 3,
+ "value": 3,
+ "minValue": 0,
+ "description": "Security System Target State",
+ "type": "67",
+ "valid-values": [
+ 1,
+ 3
+ ],
+ "iid": 66308,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "format": "uint8"
+ }
+ ],
+ "type": "7e"
+ }
+ ],
+ "aid": 1
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/homekit_controller/ecobee3.json b/tests/fixtures/homekit_controller/ecobee3.json
new file mode 100644
index 00000000000..34c3fb4cdea
--- /dev/null
+++ b/tests/fixtures/homekit_controller/ecobee3.json
@@ -0,0 +1,1036 @@
+[
+ {
+ "aid": 1,
+ "services": [
+ {
+ "type": "3E",
+ "characteristics": [
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 2
+ },
+ {
+ "value": "ecobee Inc.",
+ "perms": [
+ "pr"
+ ],
+ "type": "20",
+ "format": "string",
+ "iid": 3
+ },
+ {
+ "value": "123456789012",
+ "perms": [
+ "pr"
+ ],
+ "type": "30",
+ "format": "string",
+ "iid": 4
+ },
+ {
+ "value": "ecobee3",
+ "perms": [
+ "pr"
+ ],
+ "type": "21",
+ "format": "string",
+ "iid": 5
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "14",
+ "format": "bool",
+ "iid": 6
+ },
+ {
+ "value": "4.2.394",
+ "perms": [
+ "pr"
+ ],
+ "type": "52",
+ "format": "string",
+ "iid": 8
+ },
+ {
+ "value": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "A6",
+ "format": "uint32",
+ "iid": 9
+ }
+ ],
+ "iid": 1
+ },
+ {
+ "type": "A2",
+ "characteristics": [
+ {
+ "value": "1.1.0",
+ "perms": [
+ "pr"
+ ],
+ "maxLen": 64,
+ "type": "37",
+ "format": "string",
+ "iid": 31
+ }
+ ],
+ "iid": 30
+ },
+ {
+ "primary": true,
+ "type": "4A",
+ "characteristics": [
+ {
+ "value": 1,
+ "maxValue": 2,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "F",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 17
+ },
+ {
+ "value": 1,
+ "maxValue": 3,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "33",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 18
+ },
+ {
+ "value": 21.8,
+ "maxValue": 100,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "11",
+ "minValue": 0,
+ "format": "float",
+ "iid": 19
+ },
+ {
+ "value": 22.2,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "35",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 20
+ },
+ {
+ "value": 1,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "36",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 21
+ },
+ {
+ "value": 24.4,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "D",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 22
+ },
+ {
+ "value": 22.2,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "12",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 23
+ },
+ {
+ "value": 34,
+ "maxValue": 100,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "percentage",
+ "type": "10",
+ "minValue": 0,
+ "format": "float",
+ "iid": 24
+ },
+ {
+ "value": 36,
+ "maxValue": 50,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "percentage",
+ "type": "34",
+ "minValue": 20,
+ "format": "float",
+ "iid": 25
+ },
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 27
+ },
+ {
+ "value": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "B7DDB9A3-54BB-4572-91D2-F1F5B0510F8C",
+ "format": "uint8",
+ "iid": 33
+ },
+ {
+ "value": 22.2,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "E4489BBC-5227-4569-93E5-B345E3E5508F",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 34
+ },
+ {
+ "value": 24.4,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "7D381BAA-20F9-40E5-9BE9-AEB92D4BECEF",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 35
+ },
+ {
+ "value": 17.8,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "73AAB542-892A-4439-879A-D2A883724B69",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 36
+ },
+ {
+ "value": 27.8,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "5DA985F0-898A-4850-B987-B76C6C78D670",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 37
+ },
+ {
+ "value": 18.9,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "05B97374-6DC0-439B-A0FA-CA33F612D425",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 38
+ },
+ {
+ "value": 26.7,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "A251F6E7-AC46-4190-9C5D-3D06277BDF9F",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 39
+ },
+ {
+ "minValue": 0,
+ "maxValue": 3,
+ "minStep": 1,
+ "perms": [
+ "pw"
+ ],
+ "type": "1B300BC2-CFFC-47FF-89F9-BD6CCF5F2853",
+ "format": "uint8",
+ "iid": 40
+ },
+ {
+ "value": "2014-01-03T00:00:00-05:00",
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "1621F556-1367-443C-AF19-82AF018E99DE",
+ "format": "string",
+ "iid": 41
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "FA128DE6-9D7D-49A4-B6D8-4E4E234DEE38",
+ "format": "bool",
+ "iid": 48
+ },
+ {
+ "value": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "4A6AE4F6-036C-495D-87CC-B3702B437741",
+ "format": "uint8",
+ "iid": 49
+ },
+ {
+ "value": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "DB7BF261-7042-4194-8BD1-3AA22830AEDD",
+ "format": "uint8",
+ "iid": 50
+ },
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "41935E3E-B54D-42E9-B8B9-D33C6319F0AF",
+ "format": "bool",
+ "iid": 51
+ },
+ {
+ "minValue": 0,
+ "maxValue": 100,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "C35DA3C0-E004-40E3-B153-46655CDD9214",
+ "value": 0,
+ "format": "uint8",
+ "iid": 52
+ },
+ {
+ "value": 100,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "48F62AEC-4171-4B4A-8F0E-1EEB6708B3FB",
+ "format": "uint8",
+ "iid": 53
+ },
+ {
+ "value": "The Hive is humming along. You have no pending alerts or reminders.",
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "iid": 54,
+ "type": "1B1515F2-CC45-409F-991F-C480987F92C3",
+ "format": "string",
+ "maxLen": 256
+ }
+ ],
+ "iid": 16
+ },
+ {
+ "type": "85",
+ "characteristics": [
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 28
+ },
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "22",
+ "format": "bool",
+ "iid": 66
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
+ "value": 2980,
+ "format": "int",
+ "iid": 67
+ }
+ ],
+ "iid": 56
+ },
+ {
+ "type": "86",
+ "characteristics": [
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 29
+ },
+ {
+ "minValue": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "71",
+ "value": 1,
+ "format": "uint8",
+ "iid": 65
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "A8f798E0-4A40-11E6-BDF4-0800200C9A66",
+ "value": 2980,
+ "format": "int",
+ "iid": 68
+ }
+ ],
+ "iid": 57
+ }
+ ]
+ },
+ {
+ "aid": 2,
+ "services": [
+ {
+ "type": "3E",
+ "characteristics": [
+ {
+ "value": "Kitchen",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 2049
+ },
+ {
+ "value": "ecobee Inc.",
+ "perms": [
+ "pr"
+ ],
+ "type": "20",
+ "format": "string",
+ "iid": 2050
+ },
+ {
+ "value": "AB1C",
+ "perms": [
+ "pr"
+ ],
+ "type": "30",
+ "format": "string",
+ "iid": 2051
+ },
+ {
+ "value": "REMOTE SENSOR",
+ "perms": [
+ "pr"
+ ],
+ "type": "21",
+ "format": "string",
+ "iid": 2052
+ },
+ {
+ "value": "1.0.0",
+ "perms": [
+ "pr"
+ ],
+ "type": "52",
+ "format": "string",
+ "iid": 8
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "14",
+ "format": "bool",
+ "iid": 2053
+ }
+ ],
+ "iid": 1
+ },
+ {
+ "type": "8A",
+ "characteristics": [
+ {
+ "value": 21.5,
+ "maxValue": 100,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "11",
+ "minValue": 0,
+ "format": "float",
+ "iid": 2064
+ },
+ {
+ "value": "Kitchen",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 2067
+ },
+ {
+ "value": true,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "75",
+ "format": "bool",
+ "iid": 2066
+ },
+ {
+ "value": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "79",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 2065
+ }
+ ],
+ "iid": 55
+ },
+ {
+ "type": "85",
+ "characteristics": [
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "22",
+ "format": "bool",
+ "iid": 2060
+ },
+ {
+ "value": "Kitchen",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 2063
+ },
+ {
+ "value": true,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "75",
+ "format": "bool",
+ "iid": 2062
+ },
+ {
+ "minValue": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "79",
+ "value": 0,
+ "format": "uint8",
+ "iid": 2061
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
+ "value": 3620,
+ "format": "int",
+ "iid": 2059
+ }
+ ],
+ "iid": 56
+ }
+ ]
+ },
+ {
+ "aid": 3,
+ "services": [
+ {
+ "type": "3E",
+ "characteristics": [
+ {
+ "value": "Porch",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 3073
+ },
+ {
+ "value": "ecobee Inc.",
+ "perms": [
+ "pr"
+ ],
+ "type": "20",
+ "format": "string",
+ "iid": 3074
+ },
+ {
+ "value": "AB2C",
+ "perms": [
+ "pr"
+ ],
+ "type": "30",
+ "format": "string",
+ "iid": 3075
+ },
+ {
+ "value": "REMOTE SENSOR",
+ "perms": [
+ "pr"
+ ],
+ "type": "21",
+ "format": "string",
+ "iid": 3076
+ },
+ {
+ "value": "1.0.0",
+ "perms": [
+ "pr"
+ ],
+ "type": "52",
+ "format": "string",
+ "iid": 8
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "14",
+ "format": "bool",
+ "iid": 3077
+ }
+ ],
+ "iid": 1
+ },
+ {
+ "type": "8A",
+ "characteristics": [
+ {
+ "value": 21,
+ "maxValue": 100,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "11",
+ "minValue": 0,
+ "format": "float",
+ "iid": 3088
+ },
+ {
+ "value": "Porch",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 3091
+ },
+ {
+ "value": true,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "75",
+ "format": "bool",
+ "iid": 3090
+ },
+ {
+ "value": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "79",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 3089
+ }
+ ],
+ "iid": 55
+ },
+ {
+ "type": "85",
+ "characteristics": [
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "22",
+ "format": "bool",
+ "iid": 3084
+ },
+ {
+ "value": "Porch",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 3087
+ },
+ {
+ "value": true,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "75",
+ "format": "bool",
+ "iid": 3086
+ },
+ {
+ "minValue": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "79",
+ "value": 0,
+ "format": "uint8",
+ "iid": 3085
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
+ "value": 5766,
+ "format": "int",
+ "iid": 3083
+ }
+ ],
+ "iid": 56
+ }
+ ]
+ },
+ {
+ "aid": 4,
+ "services": [
+ {
+ "type": "3E",
+ "characteristics": [
+ {
+ "value": "Basement",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 4097
+ },
+ {
+ "value": "ecobee Inc.",
+ "perms": [
+ "pr"
+ ],
+ "type": "20",
+ "format": "string",
+ "iid": 4098
+ },
+ {
+ "value": "AB3C",
+ "perms": [
+ "pr"
+ ],
+ "type": "30",
+ "format": "string",
+ "iid": 4099
+ },
+ {
+ "value": "REMOTE SENSOR",
+ "perms": [
+ "pr"
+ ],
+ "type": "21",
+ "format": "string",
+ "iid": 4100
+ },
+ {
+ "value": "1.0.0",
+ "perms": [
+ "pr"
+ ],
+ "type": "52",
+ "format": "string",
+ "iid": 8
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "14",
+ "format": "bool",
+ "iid": 4101
+ }
+ ],
+ "iid": 1
+ },
+ {
+ "type": "8A",
+ "characteristics": [
+ {
+ "value": 20.7,
+ "maxValue": 100,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "11",
+ "minValue": 0,
+ "format": "float",
+ "iid": 4112
+ },
+ {
+ "value": "Basement",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 4115
+ },
+ {
+ "value": true,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "75",
+ "format": "bool",
+ "iid": 4114
+ },
+ {
+ "value": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "79",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 4113
+ }
+ ],
+ "iid": 55
+ },
+ {
+ "type": "85",
+ "characteristics": [
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "22",
+ "format": "bool",
+ "iid": 4108
+ },
+ {
+ "value": "Basement",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 4111
+ },
+ {
+ "value": true,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "75",
+ "format": "bool",
+ "iid": 4110
+ },
+ {
+ "minValue": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "79",
+ "value": 0,
+ "format": "uint8",
+ "iid": 4109
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
+ "value": 5472,
+ "format": "int",
+ "iid": 4107
+ }
+ ],
+ "iid": 56
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/tests/fixtures/homekit_controller/ecobee3_no_sensors.json b/tests/fixtures/homekit_controller/ecobee3_no_sensors.json
new file mode 100644
index 00000000000..3d3c2ebad2b
--- /dev/null
+++ b/tests/fixtures/homekit_controller/ecobee3_no_sensors.json
@@ -0,0 +1,508 @@
+[
+ {
+ "aid": 1,
+ "services": [
+ {
+ "type": "3E",
+ "characteristics": [
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 2
+ },
+ {
+ "value": "ecobee Inc.",
+ "perms": [
+ "pr"
+ ],
+ "type": "20",
+ "format": "string",
+ "iid": 3
+ },
+ {
+ "value": "123456789012",
+ "perms": [
+ "pr"
+ ],
+ "type": "30",
+ "format": "string",
+ "iid": 4
+ },
+ {
+ "value": "ecobee3",
+ "perms": [
+ "pr"
+ ],
+ "type": "21",
+ "format": "string",
+ "iid": 5
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "14",
+ "format": "bool",
+ "iid": 6
+ },
+ {
+ "value": "4.2.394",
+ "perms": [
+ "pr"
+ ],
+ "type": "52",
+ "format": "string",
+ "iid": 8
+ },
+ {
+ "value": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "A6",
+ "format": "uint32",
+ "iid": 9
+ }
+ ],
+ "iid": 1
+ },
+ {
+ "type": "A2",
+ "characteristics": [
+ {
+ "value": "1.1.0",
+ "perms": [
+ "pr"
+ ],
+ "maxLen": 64,
+ "type": "37",
+ "format": "string",
+ "iid": 31
+ }
+ ],
+ "iid": 30
+ },
+ {
+ "primary": true,
+ "type": "4A",
+ "characteristics": [
+ {
+ "value": 1,
+ "maxValue": 2,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "F",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 17
+ },
+ {
+ "value": 1,
+ "maxValue": 3,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "33",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 18
+ },
+ {
+ "value": 21.8,
+ "maxValue": 100,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "11",
+ "minValue": 0,
+ "format": "float",
+ "iid": 19
+ },
+ {
+ "value": 22.2,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "35",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 20
+ },
+ {
+ "value": 1,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "36",
+ "minValue": 0,
+ "format": "uint8",
+ "iid": 21
+ },
+ {
+ "value": 24.4,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "D",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 22
+ },
+ {
+ "value": 22.2,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "12",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 23
+ },
+ {
+ "value": 34,
+ "maxValue": 100,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "unit": "percentage",
+ "type": "10",
+ "minValue": 0,
+ "format": "float",
+ "iid": 24
+ },
+ {
+ "value": 36,
+ "maxValue": 50,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "percentage",
+ "type": "34",
+ "minValue": 20,
+ "format": "float",
+ "iid": 25
+ },
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 27
+ },
+ {
+ "value": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "B7DDB9A3-54BB-4572-91D2-F1F5B0510F8C",
+ "format": "uint8",
+ "iid": 33
+ },
+ {
+ "value": 22.2,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "E4489BBC-5227-4569-93E5-B345E3E5508F",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 34
+ },
+ {
+ "value": 24.4,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "7D381BAA-20F9-40E5-9BE9-AEB92D4BECEF",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 35
+ },
+ {
+ "value": 17.8,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "73AAB542-892A-4439-879A-D2A883724B69",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 36
+ },
+ {
+ "value": 27.8,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "5DA985F0-898A-4850-B987-B76C6C78D670",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 37
+ },
+ {
+ "value": 18.9,
+ "maxValue": 26.1,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "05B97374-6DC0-439B-A0FA-CA33F612D425",
+ "minValue": 7.2,
+ "format": "float",
+ "iid": 38
+ },
+ {
+ "value": 26.7,
+ "maxValue": 33.3,
+ "minStep": 0.1,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "unit": "celsius",
+ "type": "A251F6E7-AC46-4190-9C5D-3D06277BDF9F",
+ "minValue": 18.3,
+ "format": "float",
+ "iid": 39
+ },
+ {
+ "minValue": 0,
+ "maxValue": 3,
+ "minStep": 1,
+ "perms": [
+ "pw"
+ ],
+ "type": "1B300BC2-CFFC-47FF-89F9-BD6CCF5F2853",
+ "format": "uint8",
+ "iid": 40
+ },
+ {
+ "value": "2014-01-03T00:00:00-05:00",
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "1621F556-1367-443C-AF19-82AF018E99DE",
+ "format": "string",
+ "iid": 41
+ },
+ {
+ "perms": [
+ "pw"
+ ],
+ "type": "FA128DE6-9D7D-49A4-B6D8-4E4E234DEE38",
+ "format": "bool",
+ "iid": 48
+ },
+ {
+ "value": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "4A6AE4F6-036C-495D-87CC-B3702B437741",
+ "format": "uint8",
+ "iid": 49
+ },
+ {
+ "value": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "DB7BF261-7042-4194-8BD1-3AA22830AEDD",
+ "format": "uint8",
+ "iid": 50
+ },
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "41935E3E-B54D-42E9-B8B9-D33C6319F0AF",
+ "format": "bool",
+ "iid": 51
+ },
+ {
+ "minValue": 0,
+ "maxValue": 100,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "C35DA3C0-E004-40E3-B153-46655CDD9214",
+ "value": 0,
+ "format": "uint8",
+ "iid": 52
+ },
+ {
+ "value": 100,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "48F62AEC-4171-4B4A-8F0E-1EEB6708B3FB",
+ "format": "uint8",
+ "iid": 53
+ },
+ {
+ "value": "The Hive is humming along. You have no pending alerts or reminders.",
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "iid": 54,
+ "type": "1B1515F2-CC45-409F-991F-C480987F92C3",
+ "format": "string",
+ "maxLen": 256
+ }
+ ],
+ "iid": 16
+ },
+ {
+ "type": "85",
+ "characteristics": [
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 28
+ },
+ {
+ "value": false,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "22",
+ "format": "bool",
+ "iid": 66
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
+ "value": 2980,
+ "format": "int",
+ "iid": 67
+ }
+ ],
+ "iid": 56
+ },
+ {
+ "type": "86",
+ "characteristics": [
+ {
+ "value": "HomeW",
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "format": "string",
+ "iid": 29
+ },
+ {
+ "minValue": 0,
+ "maxValue": 1,
+ "minStep": 1,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "71",
+ "value": 1,
+ "format": "uint8",
+ "iid": 65
+ },
+ {
+ "minValue": -1,
+ "maxValue": 86400,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "A8f798E0-4A40-11E6-BDF4-0800200C9A66",
+ "value": 2980,
+ "format": "int",
+ "iid": 68
+ }
+ ],
+ "iid": 57
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/tests/components/homekit_controller/specific_devices/koogeek_ls1.json b/tests/fixtures/homekit_controller/koogeek_ls1.json
similarity index 100%
rename from tests/components/homekit_controller/specific_devices/koogeek_ls1.json
rename to tests/fixtures/homekit_controller/koogeek_ls1.json
diff --git a/tests/fixtures/homekit_controller/lennox_e30.json b/tests/fixtures/homekit_controller/lennox_e30.json
new file mode 100644
index 00000000000..9d2fe115259
--- /dev/null
+++ b/tests/fixtures/homekit_controller/lennox_e30.json
@@ -0,0 +1,196 @@
+[
+ {
+ "aid": 1,
+ "services": [
+ {
+ "characteristics": [
+ {
+ "format": "bool",
+ "iid": 2,
+ "perms": [
+ "pw"
+ ],
+ "type": "14"
+ },
+ {
+ "format": "string",
+ "iid": 3,
+ "perms": [
+ "pr"
+ ],
+ "type": "20",
+ "value": "Lennox"
+ },
+ {
+ "format": "string",
+ "iid": 4,
+ "perms": [
+ "pr"
+ ],
+ "type": "21",
+ "value": "E30 2B"
+ },
+ {
+ "format": "string",
+ "iid": 5,
+ "perms": [
+ "pr"
+ ],
+ "type": "23",
+ "value": "Lennox"
+ },
+ {
+ "format": "string",
+ "iid": 6,
+ "perms": [
+ "pr"
+ ],
+ "type": "30",
+ "value": "XXXXXXXX"
+ },
+ {
+ "format": "string",
+ "iid": 7,
+ "perms": [
+ "pr"
+ ],
+ "type": "52",
+ "value": "3.40.XX"
+ },
+ {
+ "format": "string",
+ "iid": 8,
+ "perms": [
+ "pr"
+ ],
+ "type": "53",
+ "value": "3.0.XX"
+ }
+ ],
+ "iid": 1,
+ "type": "3E"
+ },
+ {
+ "characteristics": [
+ {
+ "format": "uint8",
+ "iid": 101,
+ "maxValue": 2,
+ "minStep": 1,
+ "minValue": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "F",
+ "value": 1
+ },
+ {
+ "format": "uint8",
+ "iid": 102,
+ "maxValue": 3,
+ "minStep": 1,
+ "minValue": 0,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "33",
+ "value": 3
+ },
+ {
+ "format": "float",
+ "iid": 103,
+ "maxValue": 100,
+ "minStep": 0.1,
+ "minValue": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "11",
+ "unit": "celsius",
+ "value": 20.5
+ },
+ {
+ "format": "float",
+ "iid": 104,
+ "maxValue": 32,
+ "minStep": 0.5,
+ "minValue": 4.5,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "35",
+ "unit": "celsius",
+ "value": 21
+ },
+ {
+ "format": "uint8",
+ "iid": 105,
+ "maxValue": 1,
+ "minStep": 1,
+ "minValue": 0,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "36",
+ "value": 0
+ },
+ {
+ "format": "float",
+ "iid": 106,
+ "maxValue": 37,
+ "minStep": 0.5,
+ "minValue": 16,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "D",
+ "unit": "celsius",
+ "value": 29.5
+ },
+ {
+ "format": "float",
+ "iid": 107,
+ "maxValue": 100,
+ "minStep": 1,
+ "minValue": 0,
+ "perms": [
+ "pr",
+ "ev"
+ ],
+ "type": "10",
+ "unit": "percentage",
+ "value": 34
+ },
+ {
+ "format": "float",
+ "iid": 108,
+ "maxValue": 32,
+ "minStep": 0.5,
+ "minValue": 4.5,
+ "perms": [
+ "pr",
+ "pw",
+ "ev"
+ ],
+ "type": "12",
+ "unit": "celsius",
+ "value": 21
+ }
+ ],
+ "iid": 100,
+ "primary": true,
+ "type": "4A"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py
index 846c2cd1560..04c91cfdc08 100644
--- a/tests/helpers/test_config_entry_flow.py
+++ b/tests/helpers/test_config_entry_flow.py
@@ -3,9 +3,10 @@ from unittest.mock import patch, Mock
import pytest
-from homeassistant import config_entries, data_entry_flow, loader
+from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.helpers import config_entry_flow
-from tests.common import MockConfigEntry, MockModule
+from tests.common import (
+ MockConfigEntry, MockModule, mock_coro, mock_integration)
@pytest.fixture
@@ -101,7 +102,7 @@ async def test_discovery_confirmation(hass, discovery_flow_conf):
async def test_multiple_discoveries(hass, discovery_flow_conf):
"""Test we only create one instance for multiple discoveries."""
- loader.set_component(hass, 'test', MockModule('test'))
+ mock_integration(hass, MockModule('test'))
result = await hass.config_entries.flow.async_init(
'test', context={'source': config_entries.SOURCE_DISCOVERY}, data={})
@@ -115,7 +116,7 @@ async def test_multiple_discoveries(hass, discovery_flow_conf):
async def test_only_one_in_progress(hass, discovery_flow_conf):
"""Test a user initialized one will finish and cancel discovered one."""
- loader.set_component(hass, 'test', MockModule('test'))
+ mock_integration(hass, MockModule('test'))
# Discovery starts flow
result = await hass.config_entries.flow.async_init(
@@ -193,3 +194,51 @@ async def test_webhook_config_flow_registers_webhook(hass, webhook_flow_conf):
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['data']['webhook_id'] is not None
+
+
+async def test_webhook_create_cloudhook(hass, webhook_flow_conf):
+ """Test only a single entry is allowed."""
+ assert await setup.async_setup_component(hass, 'cloud', {})
+
+ async_setup_entry = Mock(return_value=mock_coro(True))
+ async_unload_entry = Mock(return_value=mock_coro(True))
+
+ mock_integration(hass, MockModule(
+ 'test_single',
+ async_setup_entry=async_setup_entry,
+ async_unload_entry=async_unload_entry,
+ async_remove_entry=config_entry_flow.webhook_async_remove_entry,
+ ))
+
+ result = await hass.config_entries.flow.async_init(
+ 'test_single', context={'source': config_entries.SOURCE_USER})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
+
+ coro = mock_coro({
+ 'cloudhook_url': 'https://example.com'
+ })
+
+ with patch('hass_nabucasa.cloudhooks.Cloudhooks.async_create',
+ return_value=coro) as mock_create, \
+ patch('homeassistant.components.cloud.async_active_subscription',
+ return_value=True), \
+ patch('homeassistant.components.cloud.async_is_logged_in',
+ return_value=True):
+
+ result = await hass.config_entries.flow.async_configure(
+ result['flow_id'], {})
+
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['description_placeholders']['webhook_url'] == \
+ 'https://example.com'
+ assert len(mock_create.mock_calls) == 1
+ assert len(async_setup_entry.mock_calls) == 1
+
+ with patch('hass_nabucasa.cloudhooks.Cloudhooks.async_delete',
+ return_value=coro) as mock_delete:
+
+ result = \
+ await hass.config_entries.async_remove(result['result'].entry_id)
+
+ assert len(mock_delete.mock_calls) == 1
+ assert result['require_restart'] is False
diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py
index 4a883fbf2fd..e0bd509d330 100644
--- a/tests/helpers/test_config_validation.py
+++ b/tests/helpers/test_config_validation.py
@@ -1,15 +1,15 @@
"""Test config validators."""
-from datetime import timedelta, datetime, date
+from datetime import date, datetime, timedelta
import enum
import os
from socket import _GLOBAL_DEFAULT_TIMEOUT
from unittest.mock import Mock, patch
import uuid
-import homeassistant
import pytest
import voluptuous as vol
+import homeassistant
import homeassistant.helpers.config_validation as cv
@@ -291,6 +291,11 @@ def test_time_period():
assert -1 * timedelta(hours=1, minutes=15) == schema('-1:15')
+def test_remove_falsy():
+ """Test remove falsy."""
+ assert cv.remove_falsy([0, None, 1, "1", {}, [], ""]) == [1, "1"]
+
+
def test_service():
"""Test service validation."""
schema = vol.Schema(cv.service)
@@ -908,7 +913,7 @@ def test_matches_regex():
schema(" nrtd ")
test_str = "This is a test including uiae."
- assert (schema(test_str) == test_str)
+ assert schema(test_str) == test_str
def test_is_regex():
@@ -982,6 +987,6 @@ def test_uuid4_hex(caplog):
# the 17th char should be 8-a
schema('a03d31b22eee4acc7b90eec40be6ed23')
- hex = uuid.uuid4().hex
- assert schema(hex) == hex
- assert schema(hex.upper()) == hex
+ _hex = uuid.uuid4().hex
+ assert schema(_hex) == _hex
+ assert schema(_hex.upper()) == _hex
diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py
index ffafd3ca146..3a7f29273e5 100644
--- a/tests/helpers/test_discovery.py
+++ b/tests/helpers/test_discovery.py
@@ -3,13 +3,14 @@ from unittest.mock import patch
import pytest
-from homeassistant import loader, setup
+from homeassistant import setup
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import discovery
from tests.common import (
- get_test_home_assistant, MockModule, MockPlatform, mock_coro)
+ get_test_home_assistant, MockModule, MockPlatform, mock_coro,
+ mock_integration, mock_entity_platform)
class TestHelpersDiscovery:
@@ -46,17 +47,17 @@ class TestHelpersDiscovery:
callback_multi)
helpers.discovery.discover('test service', 'discovery info',
- 'test_component')
+ 'test_component', {})
self.hass.block_till_done()
assert mock_setup_component.called
assert mock_setup_component.call_args[0] == \
- (self.hass, 'test_component', None)
+ (self.hass, 'test_component', {})
assert len(calls_single) == 1
assert calls_single[0] == ('test service', 'discovery info')
helpers.discovery.discover('another service', 'discovery info',
- 'test_component')
+ 'test_component', {})
self.hass.block_till_done()
assert len(calls_single) == 1
@@ -128,14 +129,18 @@ class TestHelpersDiscovery:
"""Set up mock platform."""
platform_calls.append('disc' if discovery_info else 'component')
- loader.set_component(
- self.hass, 'test_component',
+ mock_integration(
+ self.hass,
MockModule('test_component', setup=component_setup))
- loader.set_component(
+ # dependencies are only set in component level
+ # since we are using manifest to hold them
+ mock_integration(
+ self.hass,
+ MockModule('test_circular', dependencies=['test_component']))
+ mock_entity_platform(
self.hass, 'switch.test_circular',
- MockPlatform(setup_platform,
- dependencies=['test_component']))
+ MockPlatform(setup_platform))
setup.setup_component(self.hass, 'test_component', {
'test_component': None,
@@ -167,8 +172,8 @@ class TestHelpersDiscovery:
def component1_setup(hass, config):
"""Set up mock component."""
print('component1 setup')
- discovery.discover(hass, 'test_component2',
- component='test_component2')
+ discovery.discover(hass, 'test_component2', {},
+ 'test_component2', {})
return True
def component2_setup(hass, config):
@@ -176,12 +181,12 @@ class TestHelpersDiscovery:
component_calls.append(1)
return True
- loader.set_component(
- self.hass, 'test_component1',
+ mock_integration(
+ self.hass,
MockModule('test_component1', setup=component1_setup))
- loader.set_component(
- self.hass, 'test_component2',
+ mock_integration(
+ self.hass,
MockModule('test_component2', setup=component2_setup))
@callback
@@ -209,4 +214,4 @@ async def test_load_platform_forbids_config():
async def test_discover_forbids_config():
"""Test you cannot setup config component with load_platform."""
with pytest.raises(HomeAssistantError):
- await discovery.async_discover(None, None, None, 'config')
+ await discovery.async_discover(None, None, None, 'config', {})
diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py
index 6da3293d597..cb433a16a7c 100644
--- a/tests/helpers/test_entity_component.py
+++ b/tests/helpers/test_entity_component.py
@@ -10,7 +10,6 @@ from datetime import timedelta
import pytest
import homeassistant.core as ha
-import homeassistant.loader as loader
from homeassistant.exceptions import PlatformNotReady
from homeassistant.components import group
from homeassistant.helpers.entity_component import EntityComponent
@@ -21,7 +20,8 @@ import homeassistant.util.dt as dt_util
from tests.common import (
get_test_home_assistant, MockPlatform, MockModule, mock_coro,
- async_fire_time_changed, MockEntity, MockConfigEntry)
+ async_fire_time_changed, MockEntity, MockConfigEntry,
+ mock_entity_platform, mock_integration)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "test_domain"
@@ -74,11 +74,14 @@ class TestHelpersEntityComponent(unittest.TestCase):
"""Test the loading of the platforms."""
component_setup = Mock(return_value=True)
platform_setup = Mock(return_value=None)
- loader.set_component(
- self.hass, 'test_component',
- MockModule('test_component', setup=component_setup))
- loader.set_component(self.hass, 'test_domain.mod2',
- MockPlatform(platform_setup, ['test_component']))
+
+ mock_integration(self.hass,
+ MockModule('test_component', setup=component_setup))
+ # mock the dependencies
+ mock_integration(self.hass,
+ MockModule('mod2', dependencies=['test_component']))
+ mock_entity_platform(self.hass, 'test_domain.mod2',
+ MockPlatform(platform_setup))
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
@@ -100,9 +103,9 @@ class TestHelpersEntityComponent(unittest.TestCase):
platform1_setup = Mock(side_effect=Exception('Broken'))
platform2_setup = Mock(return_value=None)
- loader.set_component(self.hass, 'test_domain.mod1',
+ mock_entity_platform(self.hass, 'test_domain.mod1',
MockPlatform(platform1_setup))
- loader.set_component(self.hass, 'test_domain.mod2',
+ mock_entity_platform(self.hass, 'test_domain.mod2',
MockPlatform(platform2_setup))
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
@@ -147,7 +150,7 @@ class TestHelpersEntityComponent(unittest.TestCase):
"""Test the platform setup."""
add_entities([MockEntity(should_poll=True)])
- loader.set_component(self.hass, 'test_domain.platform',
+ mock_entity_platform(self.hass, 'test_domain.platform',
MockPlatform(platform_setup))
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
@@ -174,7 +177,7 @@ class TestHelpersEntityComponent(unittest.TestCase):
platform = MockPlatform(platform_setup)
- loader.set_component(self.hass, 'test_domain.platform', platform)
+ mock_entity_platform(self.hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
@@ -222,7 +225,8 @@ def test_platform_not_ready(hass):
"""Test that we retry when platform not ready."""
platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady,
None])
- loader.set_component(hass, 'test_domain.mod1',
+ mock_integration(hass, MockModule('mod1'))
+ mock_entity_platform(hass, 'test_domain.mod1',
MockPlatform(platform1_setup))
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -320,17 +324,13 @@ def test_setup_dependencies_platform(hass):
We're explictely testing that we process dependencies even if a component
with the same name has already been loaded.
"""
- loader.set_component(hass, 'test_component', MockModule('test_component'))
- loader.set_component(hass, 'test_component2',
- MockModule('test_component2'))
- loader.set_component(
- hass, 'test_component.test_domain',
- MockPlatform(dependencies=['test_component', 'test_component2']))
+ mock_integration(hass, MockModule('test_component',
+ dependencies=['test_component2']))
+ mock_integration(hass, MockModule('test_component2'))
+ mock_entity_platform(hass, 'test_domain.test_component', MockPlatform())
component = EntityComponent(_LOGGER, DOMAIN, hass)
- yield from async_setup_component(hass, 'test_component', {})
-
yield from component.async_setup({
DOMAIN: {
'platform': 'test_component',
@@ -345,7 +345,7 @@ def test_setup_dependencies_platform(hass):
async def test_setup_entry(hass):
"""Test setup entry calls async_setup_entry on platform."""
mock_setup_entry = Mock(return_value=mock_coro(True))
- loader.set_component(
+ mock_entity_platform(
hass, 'test_domain.entry_domain',
MockPlatform(async_setup_entry=mock_setup_entry,
scan_interval=timedelta(seconds=5)))
@@ -374,7 +374,7 @@ async def test_setup_entry_platform_not_exist(hass):
async def test_setup_entry_fails_duplicate(hass):
"""Test we don't allow setting up a config entry twice."""
mock_setup_entry = Mock(return_value=mock_coro(True))
- loader.set_component(
+ mock_entity_platform(
hass, 'test_domain.entry_domain',
MockPlatform(async_setup_entry=mock_setup_entry))
@@ -390,7 +390,7 @@ async def test_setup_entry_fails_duplicate(hass):
async def test_unload_entry_resets_platform(hass):
"""Test unloading an entry removes all entities."""
mock_setup_entry = Mock(return_value=mock_coro(True))
- loader.set_component(
+ mock_entity_platform(
hass, 'test_domain.entry_domain',
MockPlatform(async_setup_entry=mock_setup_entry))
diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py
index 6cf0bb0eeeb..0fed09b7cbc 100644
--- a/tests/helpers/test_entity_platform.py
+++ b/tests/helpers/test_entity_platform.py
@@ -8,7 +8,6 @@ from datetime import timedelta
import pytest
from homeassistant.exceptions import PlatformNotReady
-import homeassistant.loader as loader
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_component import (
EntityComponent, DEFAULT_SCAN_INTERVAL)
@@ -18,7 +17,7 @@ import homeassistant.util.dt as dt_util
from tests.common import (
get_test_home_assistant, MockPlatform, fire_time_changed, mock_registry,
- MockEntity, MockEntityPlatform, MockConfigEntry)
+ MockEntity, MockEntityPlatform, MockConfigEntry, mock_entity_platform)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "test_domain"
@@ -149,7 +148,7 @@ class TestHelpersEntityPlatform(unittest.TestCase):
platform = MockPlatform(platform_setup)
platform.SCAN_INTERVAL = timedelta(seconds=30)
- loader.set_component(self.hass, 'test_domain.platform', platform)
+ mock_entity_platform(self.hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
@@ -186,7 +185,7 @@ def test_platform_warn_slow_setup(hass):
"""Warn we log when platform setup takes a long time."""
platform = MockPlatform()
- loader.set_component(hass, 'test_domain.platform', platform)
+ mock_entity_platform(hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -199,7 +198,9 @@ def test_platform_warn_slow_setup(hass):
})
assert mock_call.called
- timeout, logger_method = mock_call.mock_calls[0][1][:2]
+ # mock_calls[0] is the warning message for component setup
+ # mock_calls[3] is the warning message for platform setup
+ timeout, logger_method = mock_call.mock_calls[3][1][:2]
assert timeout == entity_platform.SLOW_SETUP_WARNING
assert logger_method == _LOGGER.warning
@@ -220,7 +221,7 @@ def test_platform_error_slow_setup(hass, caplog):
platform = MockPlatform(async_setup_platform=setup_platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
- loader.set_component(hass, 'test_domain.test_platform', platform)
+ mock_entity_platform(hass, 'test_domain.test_platform', platform)
yield from component.async_setup({
DOMAIN: {
'platform': 'test_platform',
@@ -255,7 +256,7 @@ async def test_parallel_updates_async_platform(hass):
"""Test async platform does not have parallel_updates limit by default."""
platform = MockPlatform()
- loader.set_component(hass, 'test_domain.platform', platform)
+ mock_entity_platform(hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
@@ -285,7 +286,7 @@ async def test_parallel_updates_async_platform_with_constant(hass):
platform = MockPlatform()
platform.PARALLEL_UPDATES = 2
- loader.set_component(hass, 'test_domain.platform', platform)
+ mock_entity_platform(hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
@@ -316,7 +317,7 @@ async def test_parallel_updates_sync_platform(hass):
"""Test sync platform parallel_updates default set to 1."""
platform = MockPlatform()
- loader.set_component(hass, 'test_domain.platform', platform)
+ mock_entity_platform(hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
@@ -347,7 +348,7 @@ async def test_parallel_updates_sync_platform_with_constant(hass):
platform = MockPlatform()
platform.PARALLEL_UPDATES = 2
- loader.set_component(hass, 'test_domain.platform', platform)
+ mock_entity_platform(hass, 'test_domain.platform', platform)
component = EntityComponent(_LOGGER, DOMAIN, hass)
component._platforms = {}
diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py
index a36785b6ba0..647ca981da3 100644
--- a/tests/helpers/test_service.py
+++ b/tests/helpers/test_service.py
@@ -10,7 +10,7 @@ import pytest
# To prevent circular import when running just this file
import homeassistant.components # noqa
-from homeassistant import core as ha, loader, exceptions
+from homeassistant import core as ha, exceptions
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID
from homeassistant.setup import async_setup_component
import homeassistant.helpers.config_validation as cv
@@ -37,11 +37,15 @@ def mock_entities():
entity_id='light.kitchen',
available=True,
should_poll=False,
+ supported_features=1,
+ platform='test_domain',
)
living_room = Mock(
entity_id='light.living_room',
available=True,
should_poll=False,
+ supported_features=0,
+ platform='test_domain',
)
entities = OrderedDict()
entities[kitchen.entity_id] = kitchen
@@ -173,7 +177,7 @@ async def test_extract_entity_ids(hass):
hass.states.async_set('light.Ceiling', STATE_OFF)
hass.states.async_set('light.Kitchen', STATE_OFF)
- await loader.get_component(hass, 'group').Group.async_create_group(
+ await hass.components.group.Group.async_create_group(
hass, 'test', ['light.Ceiling', 'light.Kitchen'])
call = ha.ServiceCall('light', 'turn_on',
@@ -248,7 +252,7 @@ async def test_extract_entity_ids_from_area(hass):
@asyncio.coroutine
def test_async_get_all_descriptions(hass):
"""Test async_get_all_descriptions."""
- group = loader.get_component(hass, 'group')
+ group = hass.components.group
group_config = {group.DOMAIN: {}}
yield from async_setup_component(hass, group.DOMAIN, group_config)
descriptions = yield from service.async_get_all_descriptions(hass)
@@ -258,7 +262,7 @@ def test_async_get_all_descriptions(hass):
assert 'description' in descriptions['group']['reload']
assert 'fields' in descriptions['group']['reload']
- logger = loader.get_component(hass, 'logger')
+ logger = hass.components.logger
logger_config = {logger.DOMAIN: {}}
yield from async_setup_component(hass, logger.DOMAIN, logger_config)
descriptions = yield from service.async_get_all_descriptions(hass)
@@ -269,6 +273,19 @@ def test_async_get_all_descriptions(hass):
assert 'fields' in descriptions[logger.DOMAIN]['set_level']
+async def test_call_with_required_features(hass, mock_entities):
+ """Test service calls invoked only if entity has required feautres."""
+ test_service_mock = Mock(return_value=mock_coro())
+ await service.entity_service_call(hass, [
+ Mock(entities=mock_entities)
+ ], test_service_mock, ha.ServiceCall('test_domain', 'test_service', {
+ 'entity_id': 'all'
+ }), required_features=1)
+ assert len(mock_entities) == 2
+ # Called once because only one of the entities had the required features
+ assert test_service_mock.call_count == 1
+
+
async def test_call_context_user_not_exist(hass):
"""Check we don't allow deleted users to do things."""
with pytest.raises(exceptions.UnknownUser) as err:
@@ -406,7 +423,11 @@ async def test_register_admin_service(hass, hass_read_only_user,
calls.append(call)
hass.helpers.service.async_register_admin_service(
- 'test', 'test', mock_service, vol.Schema({})
+ 'test', 'test', mock_service
+ )
+ hass.helpers.service.async_register_admin_service(
+ 'test', 'test2', mock_service,
+ vol.Schema({vol.Required('required'): cv.boolean})
)
with pytest.raises(exceptions.UnknownUser):
@@ -423,9 +444,135 @@ async def test_register_admin_service(hass, hass_read_only_user,
))
assert len(calls) == 0
+ with pytest.raises(vol.Invalid):
+ await hass.services.async_call(
+ 'test', 'test', {'invalid': True}, blocking=True,
+ context=ha.Context(user_id=hass_admin_user.id))
+ assert len(calls) == 0
+
+ with pytest.raises(vol.Invalid):
+ await hass.services.async_call(
+ 'test', 'test2', {}, blocking=True, context=ha.Context(
+ user_id=hass_admin_user.id
+ ))
+ assert len(calls) == 0
+
await hass.services.async_call(
- 'test', 'test', {}, blocking=True, context=ha.Context(
+ 'test', 'test2', {'required': True}, blocking=True, context=ha.Context(
user_id=hass_admin_user.id
))
assert len(calls) == 1
assert calls[0].context.user_id == hass_admin_user.id
+
+
+async def test_domain_control_not_async(hass, mock_entities):
+ """Test domain verification in a service call with an unknown user."""
+ calls = []
+
+ def mock_service_log(call):
+ """Define a protected service."""
+ calls.append(call)
+
+ with pytest.raises(exceptions.HomeAssistantError):
+ hass.helpers.service.verify_domain_control(
+ 'test_domain')(mock_service_log)
+
+
+async def test_domain_control_unknown(hass, mock_entities):
+ """Test domain verification in a service call with an unknown user."""
+ calls = []
+
+ async def mock_service_log(call):
+ """Define a protected service."""
+ calls.append(call)
+
+ with patch('homeassistant.helpers.entity_registry.async_get_registry',
+ return_value=mock_coro(Mock(entities=mock_entities))):
+ protected_mock_service = hass.helpers.service.verify_domain_control(
+ 'test_domain')(mock_service_log)
+
+ hass.services.async_register(
+ 'test_domain', 'test_service', protected_mock_service, schema=None)
+
+ with pytest.raises(exceptions.UnknownUser):
+ await hass.services.async_call(
+ 'test_domain',
+ 'test_service', {},
+ blocking=True,
+ context=ha.Context(user_id='fake_user_id'))
+ assert len(calls) == 0
+
+
+async def test_domain_control_unauthorized(
+ hass, hass_read_only_user, mock_entities):
+ """Test domain verification in a service call with an unauthorized user."""
+ calls = []
+
+ async def mock_service_log(call):
+ """Define a protected service."""
+ calls.append(call)
+
+ with patch('homeassistant.helpers.entity_registry.async_get_registry',
+ return_value=mock_coro(Mock(entities=mock_entities))):
+ protected_mock_service = hass.helpers.service.verify_domain_control(
+ 'test_domain')(mock_service_log)
+
+ hass.services.async_register(
+ 'test_domain', 'test_service', protected_mock_service, schema=None)
+
+ with pytest.raises(exceptions.Unauthorized):
+ await hass.services.async_call(
+ 'test_domain',
+ 'test_service', {},
+ blocking=True,
+ context=ha.Context(user_id=hass_read_only_user.id))
+
+
+async def test_domain_control_admin(hass, hass_admin_user, mock_entities):
+ """Test domain verification in a service call with an admin user."""
+ calls = []
+
+ async def mock_service_log(call):
+ """Define a protected service."""
+ calls.append(call)
+
+ with patch('homeassistant.helpers.entity_registry.async_get_registry',
+ return_value=mock_coro(Mock(entities=mock_entities))):
+ protected_mock_service = hass.helpers.service.verify_domain_control(
+ 'test_domain')(mock_service_log)
+
+ hass.services.async_register(
+ 'test_domain', 'test_service', protected_mock_service, schema=None)
+
+ await hass.services.async_call(
+ 'test_domain',
+ 'test_service', {},
+ blocking=True,
+ context=ha.Context(user_id=hass_admin_user.id))
+
+ assert len(calls) == 1
+
+
+async def test_domain_control_no_user(hass, mock_entities):
+ """Test domain verification in a service call with no user."""
+ calls = []
+
+ async def mock_service_log(call):
+ """Define a protected service."""
+ calls.append(call)
+
+ with patch('homeassistant.helpers.entity_registry.async_get_registry',
+ return_value=mock_coro(Mock(entities=mock_entities))):
+ protected_mock_service = hass.helpers.service.verify_domain_control(
+ 'test_domain')(mock_service_log)
+
+ hass.services.async_register(
+ 'test_domain', 'test_service', protected_mock_service, schema=None)
+
+ await hass.services.async_call(
+ 'test_domain',
+ 'test_service', {},
+ blocking=True,
+ context=ha.Context(user_id=None))
+
+ assert len(calls) == 1
diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py
index 9d62f204dcd..ebf883bfe12 100644
--- a/tests/helpers/test_translation.py
+++ b/tests/helpers/test_translation.py
@@ -8,6 +8,7 @@ import pytest
from homeassistant import config_entries
import homeassistant.helpers.translation as translation
from homeassistant.setup import async_setup_component
+from tests.common import mock_coro
@pytest.fixture
@@ -52,20 +53,20 @@ async def test_component_translation_file(hass):
'test_package'
})
- assert path.normpath(translation.component_translation_file(
+ assert path.normpath(await translation.component_translation_file(
hass, 'switch.test', 'en')) == path.normpath(hass.config.path(
- 'custom_components', 'switch', '.translations', 'test.en.json'))
+ 'custom_components', 'test', '.translations', 'switch.en.json'))
- assert path.normpath(translation.component_translation_file(
+ assert path.normpath(await translation.component_translation_file(
hass, 'switch.test_embedded', 'en')) == path.normpath(hass.config.path(
'custom_components', 'test_embedded', '.translations',
'switch.en.json'))
- assert path.normpath(translation.component_translation_file(
- hass, 'test_standalone', 'en')) == path.normpath(hass.config.path(
- 'custom_components', '.translations', 'test_standalone.en.json'))
+ assert await translation.component_translation_file(
+ hass, 'test_standalone', 'en'
+ ) is None
- assert path.normpath(translation.component_translation_file(
+ assert path.normpath(await translation.component_translation_file(
hass, 'test_package', 'en')) == path.normpath(hass.config.path(
'custom_components', 'test_package', '.translations', 'en.json'))
@@ -74,9 +75,9 @@ def test_load_translations_files(hass):
"""Test the load translation files function."""
# Test one valid and one invalid file
file1 = hass.config.path(
- 'custom_components', 'switch', '.translations', 'test.en.json')
+ 'custom_components', 'test', '.translations', 'switch.en.json')
file2 = hass.config.path(
- 'custom_components', 'switch', '.translations', 'invalid.json')
+ 'custom_components', 'test', '.translations', 'invalid.json')
assert translation.load_translations_files({
'switch.test': file1,
'invalid': file2
@@ -133,7 +134,7 @@ async def test_get_translations_loads_config_flows(hass, mock_config_flows):
mock_config_flows.append('component1')
with patch.object(translation, 'component_translation_file',
- return_value='bla.json'), \
+ return_value=mock_coro('bla.json')), \
patch.object(translation, 'load_translations_files', return_value={
'component1': {'hello': 'world'}}):
translations = await translation.async_get_translations(hass, 'en')
diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py
index 217f26e71f7..dd0289d44be 100644
--- a/tests/scripts/test_check_config.py
+++ b/tests/scripts/test_check_config.py
@@ -90,7 +90,7 @@ class TestCheckConfig(unittest.TestCase):
res = check_config.check(get_test_config_dir())
assert res['components'].keys() == {'homeassistant'}
assert res['except'] == {
- check_config.ERROR_STR: ['Component not found: beer']}
+ check_config.ERROR_STR: ['Integration not found: beer']}
assert res['secret_cache'] == {}
assert res['secrets'] == {}
assert len(res['yaml_files']) == 1
@@ -104,7 +104,8 @@ class TestCheckConfig(unittest.TestCase):
assert res['components']['light'] == []
assert res['except'] == {
check_config.ERROR_STR: [
- 'Platform not found: light.beer',
+ 'Integration beer not found when trying to verify its '
+ 'light platform.',
]}
assert res['secret_cache'] == {}
assert res['secrets'] == {}
diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py
index 1b62c5244e4..7196c83b67e 100644
--- a/tests/test_bootstrap.py
+++ b/tests/test_bootstrap.py
@@ -9,7 +9,9 @@ import homeassistant.config as config_util
from homeassistant import bootstrap
import homeassistant.util.dt as dt_util
-from tests.common import patch_yaml_files, get_test_config_dir, mock_coro
+from tests.common import (
+ patch_yaml_files, get_test_config_dir, mock_coro, mock_integration,
+ MockModule)
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
VERSION_PATH = os.path.join(get_test_config_dir(), config_util.VERSION_FILE)
@@ -52,31 +54,6 @@ def test_home_assistant_core_config_validation(hass):
assert result is None
-def test_from_config_dict_not_mount_deps_folder(loop):
- """Test that we do not mount the deps folder inside from_config_dict."""
- with patch('homeassistant.bootstrap.is_virtual_env', return_value=False), \
- patch('homeassistant.core.HomeAssistant',
- return_value=Mock(loop=loop)), \
- patch('homeassistant.bootstrap.async_mount_local_lib_path',
- return_value=mock_coro()) as mock_mount, \
- patch('homeassistant.bootstrap.async_from_config_dict',
- return_value=mock_coro()):
-
- bootstrap.from_config_dict({}, config_dir='.')
- assert len(mock_mount.mock_calls) == 1
-
- with patch('homeassistant.bootstrap.is_virtual_env', return_value=True), \
- patch('homeassistant.core.HomeAssistant',
- return_value=Mock(loop=loop)), \
- patch('homeassistant.bootstrap.async_mount_local_lib_path',
- return_value=mock_coro()) as mock_mount, \
- patch('homeassistant.bootstrap.async_from_config_dict',
- return_value=mock_coro()):
-
- bootstrap.from_config_dict({}, config_dir='.')
- assert len(mock_mount.mock_calls) == 0
-
-
async def test_async_from_config_file_not_mount_deps_folder(loop):
"""Test that we not mount the deps folder inside async_from_config_file."""
hass = Mock(
@@ -108,7 +85,158 @@ async def test_async_from_config_file_not_mount_deps_folder(loop):
async def test_load_hassio(hass):
"""Test that we load Hass.io component."""
with patch.dict(os.environ, {}, clear=True):
- assert bootstrap._get_components(hass, {}) == set()
+ assert bootstrap._get_domains(hass, {}) == set()
with patch.dict(os.environ, {'HASSIO': '1'}):
- assert bootstrap._get_components(hass, {}) == {'hassio'}
+ assert bootstrap._get_domains(hass, {}) == {'hassio'}
+
+
+async def test_empty_setup(hass):
+ """Test an empty set up loads the core."""
+ await bootstrap._async_set_up_integrations(hass, {})
+ for domain in bootstrap.CORE_INTEGRATIONS:
+ assert domain in hass.config.components, domain
+
+
+async def test_core_failure_aborts(hass, caplog):
+ """Test failing core setup aborts further setup."""
+ with patch('homeassistant.components.homeassistant.async_setup',
+ return_value=mock_coro(False)):
+ await bootstrap._async_set_up_integrations(hass, {
+ 'group': {}
+ })
+
+ assert 'core failed to initialize' in caplog.text
+ # We aborted early, group not set up
+ assert 'group' not in hass.config.components
+
+
+async def test_setting_up_config(hass, caplog):
+ """Test we set up domains in config."""
+ await bootstrap._async_set_up_integrations(hass, {
+ 'group hello': {},
+ 'homeassistant': {}
+ })
+
+ assert 'group' in hass.config.components
+
+
+async def test_setup_after_deps_all_present(hass, caplog):
+ """Test after_dependencies when all present."""
+ caplog.set_level(logging.DEBUG)
+ order = []
+
+ def gen_domain_setup(domain):
+ async def async_setup(hass, config):
+ order.append(domain)
+ return True
+
+ return async_setup
+
+ mock_integration(hass, MockModule(
+ domain='root',
+ async_setup=gen_domain_setup('root')
+ ))
+ mock_integration(hass, MockModule(
+ domain='first_dep',
+ async_setup=gen_domain_setup('first_dep'),
+ partial_manifest={
+ 'after_dependencies': ['root']
+ }
+ ))
+ mock_integration(hass, MockModule(
+ domain='second_dep',
+ async_setup=gen_domain_setup('second_dep'),
+ partial_manifest={
+ 'after_dependencies': ['first_dep']
+ }
+ ))
+
+ await bootstrap._async_set_up_integrations(hass, {
+ 'root': {},
+ 'first_dep': {},
+ 'second_dep': {},
+ })
+
+ assert 'root' in hass.config.components
+ assert 'first_dep' in hass.config.components
+ assert 'second_dep' in hass.config.components
+ assert order == ['root', 'first_dep', 'second_dep']
+
+
+async def test_setup_after_deps_not_trigger_load(hass, caplog):
+ """Test after_dependencies does not trigger loading it."""
+ caplog.set_level(logging.DEBUG)
+ order = []
+
+ def gen_domain_setup(domain):
+ async def async_setup(hass, config):
+ order.append(domain)
+ return True
+
+ return async_setup
+
+ mock_integration(hass, MockModule(
+ domain='root',
+ async_setup=gen_domain_setup('root')
+ ))
+ mock_integration(hass, MockModule(
+ domain='first_dep',
+ async_setup=gen_domain_setup('first_dep'),
+ partial_manifest={
+ 'after_dependencies': ['root']
+ }
+ ))
+ mock_integration(hass, MockModule(
+ domain='second_dep',
+ async_setup=gen_domain_setup('second_dep'),
+ partial_manifest={
+ 'after_dependencies': ['first_dep']
+ }
+ ))
+
+ await bootstrap._async_set_up_integrations(hass, {
+ 'root': {},
+ 'second_dep': {},
+ })
+
+ assert 'root' in hass.config.components
+ assert 'first_dep' not in hass.config.components
+ assert 'second_dep' in hass.config.components
+ assert order == ['root', 'second_dep']
+
+
+async def test_setup_after_deps_not_present(hass, caplog):
+ """Test after_dependencies when referenced integration doesn't exist."""
+ caplog.set_level(logging.DEBUG)
+ order = []
+
+ def gen_domain_setup(domain):
+ async def async_setup(hass, config):
+ order.append(domain)
+ return True
+
+ return async_setup
+
+ mock_integration(hass, MockModule(
+ domain='root',
+ async_setup=gen_domain_setup('root')
+ ))
+ mock_integration(hass, MockModule(
+ domain='second_dep',
+ async_setup=gen_domain_setup('second_dep'),
+ partial_manifest={
+ 'after_dependencies': ['first_dep']
+ }
+ ))
+
+ await bootstrap._async_set_up_integrations(hass, {
+ 'root': {},
+ 'first_dep': {},
+ 'second_dep': {},
+ })
+
+ assert 'root' in hass.config.components
+ assert 'first_dep' not in hass.config.components
+ assert 'second_dep' in hass.config.components
+ assert order == ['root', 'second_dep']
diff --git a/tests/test_config.py b/tests/test_config.py
index 8afad09c946..e9ca2a6c806 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -14,6 +14,7 @@ import yaml
from homeassistant.core import DOMAIN, HomeAssistantError, Config
import homeassistant.config as config_util
+from homeassistant.loader import async_get_integration
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ASSUMED_STATE,
CONF_LATITUDE, CONF_LONGITUDE, CONF_UNIT_SYSTEM, CONF_NAME,
@@ -314,10 +315,11 @@ class TestConfig(unittest.TestCase):
def test_process_config_upgrade(self):
"""Test update of version on upgrade."""
- ha_version = '0.8.0'
+ ha_version = '0.92.0'
mock_open = mock.mock_open()
- with mock.patch('homeassistant.config.open', mock_open, create=True):
+ with mock.patch('homeassistant.config.open', mock_open, create=True), \
+ mock.patch.object(config_util, '__version__', '0.91.0'):
opened_file = mock_open.return_value
# pylint: disable=no-member
opened_file.readline.return_value = ha_version
@@ -325,7 +327,7 @@ class TestConfig(unittest.TestCase):
config_util.process_ha_config_upgrade(self.hass)
assert opened_file.write.call_count == 1
- assert opened_file.write.call_args == mock.call(__version__)
+ assert opened_file.write.call_args == mock.call('0.91.0')
def test_config_upgrade_same_version(self):
"""Test no update of version on no upgrade."""
@@ -341,10 +343,13 @@ class TestConfig(unittest.TestCase):
assert opened_file.write.call_count == 0
+ @mock.patch('homeassistant.config.find_config_file', mock.Mock())
def test_config_upgrade_no_file(self):
"""Test update of version on upgrade, with no version file."""
mock_open = mock.mock_open()
- mock_open.side_effect = [FileNotFoundError(), mock.DEFAULT]
+ mock_open.side_effect = [FileNotFoundError(),
+ mock.DEFAULT,
+ mock.DEFAULT]
with mock.patch('homeassistant.config.open', mock_open, create=True):
opened_file = mock_open.return_value
# pylint: disable=no-member
@@ -354,6 +359,7 @@ class TestConfig(unittest.TestCase):
@mock.patch('homeassistant.config.shutil')
@mock.patch('homeassistant.config.os')
+ @mock.patch('homeassistant.config.find_config_file', mock.Mock())
def test_migrate_file_on_upgrade(self, mock_os, mock_shutil):
"""Test migrate of config files on upgrade."""
ha_version = '0.7.0'
@@ -380,6 +386,7 @@ class TestConfig(unittest.TestCase):
@mock.patch('homeassistant.config.shutil')
@mock.patch('homeassistant.config.os')
+ @mock.patch('homeassistant.config.find_config_file', mock.Mock())
def test_migrate_no_file_on_upgrade(self, mock_os, mock_shutil):
"""Test not migrating config files on upgrade."""
ha_version = '0.7.0'
@@ -533,7 +540,8 @@ class TestConfig(unittest.TestCase):
assert len(self.hass.config.whitelist_external_dirs) == 1
assert "/test/config/www" in self.hass.config.whitelist_external_dirs
- @mock.patch('homeassistant.scripts.check_config.check_ha_config_file')
+ @asynctest.mock.patch(
+ 'homeassistant.scripts.check_config.check_ha_config_file')
def test_check_ha_config_file_correct(self, mock_check):
"""Check that restart propagates to stop."""
mock_check.return_value = check_config.HomeAssistantConfig()
@@ -542,7 +550,8 @@ class TestConfig(unittest.TestCase):
self.hass.loop
).result() is None
- @mock.patch('homeassistant.scripts.check_config.check_ha_config_file')
+ @asynctest.mock.patch(
+ 'homeassistant.scripts.check_config.check_ha_config_file')
def test_check_ha_config_file_wrong(self, mock_check):
"""Check that restart with a bad config doesn't propagate to stop."""
mock_check.return_value = check_config.HomeAssistantConfig()
@@ -587,7 +596,7 @@ def merge_log_err(hass):
yield logerr
-def test_merge(merge_log_err, hass):
+async def test_merge(merge_log_err, hass):
"""Test if we can merge packages."""
packages = {
'pack_dict': {'input_boolean': {'ib1': None}},
@@ -601,7 +610,7 @@ def test_merge(merge_log_err, hass):
'input_boolean': {'ib2': None},
'light': {'platform': 'test'}
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 0
assert len(config) == 5
@@ -611,7 +620,7 @@ def test_merge(merge_log_err, hass):
assert isinstance(config['wake_on_lan'], OrderedDict)
-def test_merge_try_falsy(merge_log_err, hass):
+async def test_merge_try_falsy(merge_log_err, hass):
"""Ensure we dont add falsy items like empty OrderedDict() to list."""
packages = {
'pack_falsy_to_lst': {'automation': OrderedDict()},
@@ -622,7 +631,7 @@ def test_merge_try_falsy(merge_log_err, hass):
'automation': {'do': 'something'},
'light': {'some': 'light'},
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 0
assert len(config) == 3
@@ -630,7 +639,7 @@ def test_merge_try_falsy(merge_log_err, hass):
assert len(config['light']) == 1
-def test_merge_new(merge_log_err, hass):
+async def test_merge_new(merge_log_err, hass):
"""Test adding new components to outer scope."""
packages = {
'pack_1': {'light': [{'platform': 'one'}]},
@@ -643,7 +652,7 @@ def test_merge_new(merge_log_err, hass):
config = {
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 0
assert 'api' in config
@@ -652,7 +661,7 @@ def test_merge_new(merge_log_err, hass):
assert len(config['panel_custom']) == 1
-def test_merge_type_mismatch(merge_log_err, hass):
+async def test_merge_type_mismatch(merge_log_err, hass):
"""Test if we have a type mismatch for packages."""
packages = {
'pack_1': {'input_boolean': [{'ib1': None}]},
@@ -665,7 +674,7 @@ def test_merge_type_mismatch(merge_log_err, hass):
'input_select': [{'ib2': None}],
'light': [{'platform': 'two'}]
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 2
assert len(config) == 4
@@ -673,14 +682,14 @@ def test_merge_type_mismatch(merge_log_err, hass):
assert len(config['light']) == 2
-def test_merge_once_only_keys(merge_log_err, hass):
+async def test_merge_once_only_keys(merge_log_err, hass):
"""Test if we have a merge for a comp that may occur only once. Keys."""
packages = {'pack_2': {'api': None}}
config = {
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
'api': None,
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert config['api'] == OrderedDict()
packages = {'pack_2': {'api': {
@@ -693,7 +702,7 @@ def test_merge_once_only_keys(merge_log_err, hass):
'key_2': 2,
}
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert config['api'] == {'key_1': 1, 'key_2': 2, 'key_3': 3, }
# Duplicate keys error
@@ -704,11 +713,11 @@ def test_merge_once_only_keys(merge_log_err, hass):
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
'api': {'key': 1, }
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 1
-def test_merge_once_only_lists(hass):
+async def test_merge_once_only_lists(hass):
"""Test if we have a merge for a comp that may occur only once. Lists."""
packages = {'pack_2': {'api': {
'list_1': ['item_2', 'item_3'],
@@ -721,14 +730,14 @@ def test_merge_once_only_lists(hass):
'list_1': ['item_1'],
}
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert config['api'] == {
'list_1': ['item_1', 'item_2', 'item_3'],
'list_2': ['item_1'],
}
-def test_merge_once_only_dictionaries(hass):
+async def test_merge_once_only_dictionaries(hass):
"""Test if we have a merge for a comp that may occur only once. Dicts."""
packages = {'pack_2': {'api': {
'dict_1': {
@@ -747,7 +756,7 @@ def test_merge_once_only_dictionaries(hass):
},
}
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert config['api'] == {
'dict_1': {
'key_1': 1,
@@ -758,7 +767,7 @@ def test_merge_once_only_dictionaries(hass):
}
-def test_merge_id_schema(hass):
+async def test_merge_id_schema(hass):
"""Test if we identify the config schemas correctly."""
types = {
'panel_custom': 'list',
@@ -768,14 +777,15 @@ def test_merge_id_schema(hass):
'shell_command': 'dict',
'qwikswitch': 'dict',
}
- for name, expected_type in types.items():
- module = config_util.get_component(hass, name)
+ for domain, expected_type in types.items():
+ integration = await async_get_integration(hass, domain)
+ module = integration.get_component()
typ, _ = config_util._identify_config_schema(module)
assert typ == expected_type, "{} expected {}, got {}".format(
- name, expected_type, typ)
+ domain, expected_type, typ)
-def test_merge_duplicate_keys(merge_log_err, hass):
+async def test_merge_duplicate_keys(merge_log_err, hass):
"""Test if keys in dicts are duplicates."""
packages = {
'pack_1': {'input_select': {'ib1': None}},
@@ -784,7 +794,7 @@ def test_merge_duplicate_keys(merge_log_err, hass):
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
'input_select': {'ib1': 1},
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert merge_log_err.call_count == 1
assert len(config) == 2
@@ -984,7 +994,7 @@ async def test_disallowed_duplicated_auth_mfa_module_config(hass):
await config_util.async_process_ha_core_config(hass, core_config)
-def test_merge_split_component_definition(hass):
+async def test_merge_split_component_definition(hass):
"""Test components with trailing description in packages are merged."""
packages = {
'pack_1': {'light one': {'l1': None}},
@@ -994,7 +1004,7 @@ def test_merge_split_component_definition(hass):
config = {
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
}
- config_util.merge_packages_config(hass, config, packages)
+ await config_util.merge_packages_config(hass, config, packages)
assert len(config) == 4
assert len(config['light one']) == 1
diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py
index 32532761ccf..a8a1211f4c2 100644
--- a/tests/test_config_entries.py
+++ b/tests/test_config_entries.py
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
-from homeassistant import config_entries, loader, data_entry_flow
+from homeassistant import config_entries, data_entry_flow
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.setup import async_setup_component
@@ -13,15 +13,22 @@ from homeassistant.util import dt
from tests.common import (
MockModule, mock_coro, MockConfigEntry, async_fire_time_changed,
- MockPlatform, MockEntity)
+ MockPlatform, MockEntity, mock_integration, mock_entity_platform)
-@config_entries.HANDLERS.register('test')
-@config_entries.HANDLERS.register('comp')
-class MockFlowHandler(config_entries.ConfigFlow):
- """Define a mock flow handler."""
+@pytest.fixture(autouse=True)
+def mock_handlers():
+ """Mock config flows."""
+ class MockFlowHandler(config_entries.ConfigFlow):
+ """Define a mock flow handler."""
- VERSION = 1
+ VERSION = 1
+
+ with patch.dict(config_entries.HANDLERS, {
+ 'comp': MockFlowHandler,
+ 'test': MockFlowHandler,
+ }):
+ yield
@pytest.fixture
@@ -42,8 +49,8 @@ async def test_call_setup_entry(hass):
mock_setup_entry = MagicMock(return_value=mock_coro(True))
mock_migrate_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry))
@@ -63,8 +70,8 @@ async def test_call_async_migrate_entry(hass):
mock_migrate_entry = MagicMock(return_value=mock_coro(True))
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry))
@@ -84,8 +91,8 @@ async def test_call_async_migrate_entry_failure_false(hass):
mock_migrate_entry = MagicMock(return_value=mock_coro(False))
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry))
@@ -106,8 +113,8 @@ async def test_call_async_migrate_entry_failure_exception(hass):
return_value=mock_coro(exception=Exception))
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry))
@@ -128,8 +135,8 @@ async def test_call_async_migrate_entry_failure_not_bool(hass):
return_value=mock_coro())
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry,
async_migrate_entry=mock_migrate_entry))
@@ -148,8 +155,8 @@ async def test_call_async_migrate_entry_failure_not_supported(hass):
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry))
result = await async_setup_component(hass, 'comp', {})
@@ -162,7 +169,7 @@ async def test_remove_entry(hass, manager):
"""Test that we can remove an entry."""
async def mock_setup_entry(hass, entry):
"""Mock setting up entry."""
- hass.loop.create_task(hass.config_entries.async_forward_entry_setup(
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, 'light'))
return True
@@ -185,23 +192,27 @@ async def test_remove_entry(hass, manager):
"""Mock setting up platform."""
async_add_entities([entity])
- loader.set_component(hass, 'test', MockModule(
+ mock_integration(hass, MockModule(
'test',
async_setup_entry=mock_setup_entry,
async_unload_entry=mock_unload_entry,
async_remove_entry=mock_remove_entry
))
- loader.set_component(
- hass, 'test.light',
+ mock_entity_platform(
+ hass, 'light.test',
MockPlatform(async_setup_entry=mock_setup_entry_platform))
- MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
+ MockConfigEntry(
+ domain='test_other', entry_id='test1'
+ ).add_to_manager(manager)
entry = MockConfigEntry(
domain='test',
entry_id='test2',
)
entry.add_to_manager(manager)
- MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
+ MockConfigEntry(
+ domain='test_other', entry_id='test3'
+ ).add_to_manager(manager)
# Check all config entries exist
assert [item.entry_id for item in manager.async_entries()] == \
@@ -254,7 +265,7 @@ async def test_remove_entry_handles_callback_error(hass, manager):
mock_unload_entry = MagicMock(return_value=mock_coro(True))
mock_remove_entry = MagicMock(
side_effect=lambda *args, **kwargs: mock_coro())
- loader.set_component(hass, 'test', MockModule(
+ mock_integration(hass, MockModule(
'test',
async_setup_entry=mock_setup_entry,
async_unload_entry=mock_unload_entry,
@@ -293,13 +304,12 @@ def test_remove_entry_raises(hass, manager):
"""Mock unload entry function."""
raise Exception("BROKEN")
- loader.set_component(
- hass, 'test',
- MockModule('comp', async_unload_entry=mock_unload_entry))
+ mock_integration(hass, MockModule(
+ 'comp', async_unload_entry=mock_unload_entry))
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
MockConfigEntry(
- domain='test',
+ domain='comp',
entry_id='test2',
state=config_entries.ENTRY_STATE_LOADED
).add_to_manager(manager)
@@ -319,15 +329,14 @@ def test_remove_entry_raises(hass, manager):
@asyncio.coroutine
def test_remove_entry_if_not_loaded(hass, manager):
- """Test that we can remove an entry."""
+ """Test that we can remove an entry that is not loaded."""
mock_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'test',
- MockModule('comp', async_unload_entry=mock_unload_entry))
+ mock_integration(hass, MockModule(
+ 'comp', async_unload_entry=mock_unload_entry))
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
- MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager)
+ MockConfigEntry(domain='comp', entry_id='test2').add_to_manager(manager)
MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
assert [item.entry_id for item in manager.async_entries()] == \
@@ -341,7 +350,7 @@ def test_remove_entry_if_not_loaded(hass, manager):
assert [item.entry_id for item in manager.async_entries()] == \
['test1', 'test3']
- assert len(mock_unload_entry.mock_calls) == 1
+ assert len(mock_unload_entry.mock_calls) == 0
@asyncio.coroutine
@@ -349,8 +358,8 @@ def test_add_entry_calls_setup_entry(hass, manager):
"""Test we call setup_config_entry."""
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'comp',
+ mock_integration(
+ hass,
MockModule('comp', async_setup_entry=mock_setup_entry))
class TestFlow(config_entries.ConfigFlow):
@@ -405,9 +414,8 @@ def test_domains_gets_uniques(manager):
async def test_saving_and_loading(hass):
"""Test that we're saving and loading correctly."""
- loader.set_component(
- hass, 'test',
- MockModule('test', async_setup_entry=lambda *args: mock_coro(True)))
+ mock_integration(hass, MockModule(
+ 'test', async_setup_entry=lambda *args: mock_coro(True)))
class TestFlow(config_entries.ConfigFlow):
VERSION = 5
@@ -469,13 +477,13 @@ async def test_forward_entry_sets_up_component(hass):
entry = MockConfigEntry(domain='original')
mock_original_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'original',
+ mock_integration(
+ hass,
MockModule('original', async_setup_entry=mock_original_setup_entry))
mock_forwarded_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(
- hass, 'forwarded',
+ mock_integration(
+ hass,
MockModule('forwarded', async_setup_entry=mock_forwarded_setup_entry))
await hass.config_entries.async_forward_entry_setup(entry, 'forwarded')
@@ -489,7 +497,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
mock_setup = MagicMock(return_value=mock_coro(False))
mock_setup_entry = MagicMock()
- hass, loader.set_component(hass, 'forwarded', MockModule(
+ mock_integration(hass, MockModule(
'forwarded',
async_setup=mock_setup,
async_setup_entry=mock_setup_entry,
@@ -502,7 +510,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
async def test_discovery_notification(hass):
"""Test that we create/dismiss a notification when source is discovery."""
- loader.set_component(hass, 'test', MockModule('test'))
+ mock_integration(hass, MockModule('test'))
await async_setup_component(hass, 'persistent_notification', {})
class TestFlow(config_entries.ConfigFlow):
@@ -539,7 +547,7 @@ async def test_discovery_notification(hass):
async def test_discovery_notification_not_created(hass):
"""Test that we not create a notification when discovery is aborted."""
- loader.set_component(hass, 'test', MockModule('test'))
+ mock_integration(hass, MockModule('test'))
await async_setup_component(hass, 'persistent_notification', {})
class TestFlow(config_entries.ConfigFlow):
@@ -619,8 +627,8 @@ async def test_setup_raise_not_ready(hass, caplog):
entry = MockConfigEntry(domain='test')
mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
- loader.set_component(
- hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry))
+ mock_integration(
+ hass, MockModule('test', async_setup_entry=mock_setup_entry))
with patch('homeassistant.helpers.event.async_call_later') as mock_call:
await entry.async_setup(hass)
@@ -645,8 +653,8 @@ async def test_setup_retrying_during_unload(hass):
entry = MockConfigEntry(domain='test')
mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
- loader.set_component(
- hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry))
+ mock_integration(
+ hass, MockModule('test', async_setup_entry=mock_setup_entry))
with patch('homeassistant.helpers.event.async_call_later') as mock_call:
await entry.async_setup(hass)
@@ -707,7 +715,7 @@ async def test_entry_setup_succeed(hass, manager):
mock_setup = MagicMock(return_value=mock_coro(True))
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_setup=mock_setup,
async_setup_entry=mock_setup_entry
@@ -737,7 +745,7 @@ async def test_entry_setup_invalid_state(hass, manager, state):
mock_setup = MagicMock(return_value=mock_coro(True))
mock_setup_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_setup=mock_setup,
async_setup_entry=mock_setup_entry
@@ -761,7 +769,7 @@ async def test_entry_unload_succeed(hass, manager):
async_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_unload_entry=async_unload_entry
))
@@ -786,7 +794,7 @@ async def test_entry_unload_failed_to_load(hass, manager, state):
async_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_unload_entry=async_unload_entry
))
@@ -810,7 +818,7 @@ async def test_entry_unload_invalid_state(hass, manager, state):
async_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_unload_entry=async_unload_entry
))
@@ -834,7 +842,7 @@ async def test_entry_reload_succeed(hass, manager):
async_setup_entry = MagicMock(return_value=mock_coro(True))
async_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_setup=async_setup,
async_setup_entry=async_setup_entry,
@@ -865,7 +873,7 @@ async def test_entry_reload_not_loaded(hass, manager, state):
async_setup_entry = MagicMock(return_value=mock_coro(True))
async_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_setup=async_setup,
async_setup_entry=async_setup_entry,
@@ -895,7 +903,7 @@ async def test_entry_reload_error(hass, manager, state):
async_setup_entry = MagicMock(return_value=mock_coro(True))
async_unload_entry = MagicMock(return_value=mock_coro(True))
- loader.set_component(hass, 'comp', MockModule(
+ mock_integration(hass, MockModule(
'comp',
async_setup=async_setup,
async_setup_entry=async_setup_entry,
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 09f830a8eab..8af000c5d05 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -1,52 +1,37 @@
"""Test to verify that we can load components."""
-import asyncio
-
import pytest
import homeassistant.loader as loader
-import homeassistant.components.http as http
+from homeassistant.components import http, hue
+from homeassistant.components.hue import light as hue_light
-from tests.common import MockModule, async_mock_service
+from tests.common import MockModule, async_mock_service, mock_integration
-def test_set_component(hass):
- """Test if set_component works."""
- comp = object()
- loader.set_component(hass, 'switch.test_set', comp)
-
- assert loader.get_component(hass, 'switch.test_set') is comp
-
-
-def test_get_component(hass):
- """Test if get_component works."""
- assert http == loader.get_component(hass, 'http')
-
-
-def test_component_dependencies(hass):
+async def test_component_dependencies(hass):
"""Test if we can get the proper load order of components."""
- loader.set_component(hass, 'mod1', MockModule('mod1'))
- loader.set_component(hass, 'mod2', MockModule('mod2', ['mod1']))
- loader.set_component(hass, 'mod3', MockModule('mod3', ['mod2']))
+ mock_integration(hass, MockModule('mod1'))
+ mock_integration(hass, MockModule('mod2', ['mod1']))
+ mock_integration(hass, MockModule('mod3', ['mod2']))
assert {'mod1', 'mod2', 'mod3'} == \
- loader.component_dependencies(hass, 'mod3')
+ await loader.async_component_dependencies(hass, 'mod3')
# Create circular dependency
- loader.set_component(hass, 'mod1', MockModule('mod1', ['mod3']))
+ mock_integration(hass, MockModule('mod1', ['mod3']))
with pytest.raises(loader.CircularDependency):
- print(loader.component_dependencies(hass, 'mod3'))
+ print(await loader.async_component_dependencies(hass, 'mod3'))
# Depend on non-existing component
- loader.set_component(hass, 'mod1',
- MockModule('mod1', ['nonexisting']))
+ mock_integration(hass, MockModule('mod1', ['nonexisting']))
- with pytest.raises(loader.ComponentNotFound):
- print(loader.component_dependencies(hass, 'mod1'))
+ with pytest.raises(loader.IntegrationNotFound):
+ print(await loader.async_component_dependencies(hass, 'mod1'))
# Try to get dependencies for non-existing component
- with pytest.raises(loader.ComponentNotFound):
- print(loader.component_dependencies(hass, 'nonexisting'))
+ with pytest.raises(loader.IntegrationNotFound):
+ print(await loader.async_component_dependencies(hass, 'nonexisting'))
def test_component_loader(hass):
@@ -63,20 +48,18 @@ def test_component_loader_non_existing(hass):
components.non_existing
-@asyncio.coroutine
-def test_component_wrapper(hass):
+async def test_component_wrapper(hass):
"""Test component wrapper."""
calls = async_mock_service(hass, 'persistent_notification', 'create')
components = loader.Components(hass)
components.persistent_notification.async_create('message')
- yield from hass.async_block_till_done()
+ await hass.async_block_till_done()
assert len(calls) == 1
-@asyncio.coroutine
-def test_helpers_wrapper(hass):
+async def test_helpers_wrapper(hass):
"""Test helpers wrapper."""
helpers = loader.Helpers(hass)
@@ -88,25 +71,36 @@ def test_helpers_wrapper(hass):
helpers.discovery.async_listen('service_name', discovery_callback)
- yield from helpers.discovery.async_discover('service_name', 'hello')
- yield from hass.async_block_till_done()
+ await helpers.discovery.async_discover('service_name', 'hello', None, {})
+ await hass.async_block_till_done()
assert result == ['hello']
async def test_custom_component_name(hass):
"""Test the name attribte of custom components."""
- comp = loader.get_component(hass, 'test_standalone')
+ integration = await loader.async_get_integration(hass, 'test_standalone')
+ int_comp = integration.get_component()
+ assert int_comp.__name__ == 'custom_components.test_standalone'
+ assert int_comp.__package__ == 'custom_components'
+
+ comp = hass.components.test_standalone
assert comp.__name__ == 'custom_components.test_standalone'
assert comp.__package__ == 'custom_components'
- comp = loader.get_component(hass, 'test_package')
+ integration = await loader.async_get_integration(hass, 'test_package')
+ int_comp = integration.get_component()
+ assert int_comp.__name__ == 'custom_components.test_package'
+ assert int_comp.__package__ == 'custom_components.test_package'
+
+ comp = hass.components.test_package
assert comp.__name__ == 'custom_components.test_package'
assert comp.__package__ == 'custom_components.test_package'
- comp = loader.get_component(hass, 'light.test')
- assert comp.__name__ == 'custom_components.light.test'
- assert comp.__package__ == 'custom_components.light'
+ integration = await loader.async_get_integration(hass, 'test')
+ platform = integration.get_platform('light')
+ assert platform.__name__ == 'custom_components.test.light'
+ assert platform.__package__ == 'custom_components.test'
# Test custom components is mounted
from custom_components.test_package import TEST
@@ -115,30 +109,56 @@ async def test_custom_component_name(hass):
async def test_log_warning_custom_component(hass, caplog):
"""Test that we log a warning when loading a custom component."""
- loader.get_component(hass, 'test_standalone')
- assert \
- 'You are using a custom component for test_standalone' in caplog.text
+ hass.components.test_standalone
+ assert 'You are using a custom integration for test_standalone' \
+ in caplog.text
- loader.get_component(hass, 'light.test')
- assert 'You are using a custom component for light.test' in caplog.text
+ await loader.async_get_integration(hass, 'test')
+ assert 'You are using a custom integration for test ' in caplog.text
-async def test_get_platform(hass, caplog):
- """Test get_platform."""
- # Test we prefer embedded over normal platforms."""
- embedded_platform = loader.get_platform(hass, 'switch', 'test_embedded')
- assert embedded_platform.__name__ == \
- 'custom_components.test_embedded.switch'
-
- caplog.clear()
-
- legacy_platform = loader.get_platform(hass, 'switch', 'test')
- assert legacy_platform.__name__ == 'custom_components.switch.test'
- assert 'Integrations need to be in their own folder.' in caplog.text
+async def test_get_integration(hass):
+ """Test resolving integration."""
+ integration = await loader.async_get_integration(hass, 'hue')
+ assert hue == integration.get_component()
+ assert hue_light == integration.get_platform('light')
-async def test_get_platform_enforces_component_path(hass, caplog):
- """Test that existence of a component limits lookup path of platforms."""
- assert loader.get_platform(hass, 'comp_path_test', 'hue') is None
- assert ('Search path was limited to path of component: '
- 'homeassistant.components') in caplog.text
+async def test_get_integration_legacy(hass):
+ """Test resolving integration."""
+ integration = await loader.async_get_integration(hass, 'test_embedded')
+ assert integration.get_component().DOMAIN == 'test_embedded'
+ assert integration.get_platform('switch') is not None
+
+
+async def test_get_integration_custom_component(hass):
+ """Test resolving integration."""
+ integration = await loader.async_get_integration(hass, 'test_package')
+ print(integration)
+ assert integration.get_component().DOMAIN == 'test_package'
+ assert integration.name == 'Test Package'
+
+
+def test_integration_properties(hass):
+ """Test integration properties."""
+ integration = loader.Integration(
+ hass, 'homeassistant.components.hue', None, {
+ 'name': 'Philips Hue',
+ 'domain': 'hue',
+ 'dependencies': ['test-dep'],
+ 'requirements': ['test-req==1.0.0'],
+ })
+ assert integration.name == "Philips Hue"
+ assert integration.domain == 'hue'
+ assert integration.dependencies == ['test-dep']
+ assert integration.requirements == ['test-req==1.0.0']
+
+
+async def test_integrations_only_once(hass):
+ """Test that we load integrations only once."""
+ int_1 = hass.async_create_task(
+ loader.async_get_integration(hass, 'hue'))
+ int_2 = hass.async_create_task(
+ loader.async_get_integration(hass, 'hue'))
+
+ assert await int_1 is await int_2
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index 71ae80f22e4..dcc107ea07e 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -2,13 +2,14 @@
import os
from unittest.mock import patch, call
-from homeassistant import loader, setup
+from homeassistant import setup
from homeassistant.requirements import (
CONSTRAINT_FILE, PackageLoadable, async_process_requirements)
import pkg_resources
-from tests.common import get_test_home_assistant, MockModule, mock_coro
+from tests.common import (
+ get_test_home_assistant, MockModule, mock_coro, mock_integration)
RESOURCE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'resources'))
@@ -43,10 +44,10 @@ class TestRequirements:
mock_venv.return_value = True
mock_dirname.return_value = 'ha_package_path'
self.hass.config.skip_pip = False
- loader.set_component(
- self.hass, 'comp',
+ mock_integration(
+ self.hass,
MockModule('comp', requirements=['package==0.0.1']))
- assert setup.setup_component(self.hass, 'comp')
+ assert setup.setup_component(self.hass, 'comp', {})
assert 'comp' in self.hass.config.components
assert mock_install.call_args == call(
'package==0.0.1',
@@ -60,10 +61,10 @@ class TestRequirements:
"""Test requirement installed in deps directory."""
mock_dirname.return_value = 'ha_package_path'
self.hass.config.skip_pip = False
- loader.set_component(
- self.hass, 'comp',
+ mock_integration(
+ self.hass,
MockModule('comp', requirements=['package==0.0.1']))
- assert setup.setup_component(self.hass, 'comp')
+ assert setup.setup_component(self.hass, 'comp', {})
assert 'comp' in self.hass.config.components
assert mock_install.call_args == call(
'package==0.0.1', target=self.hass.config.path('deps'),
diff --git a/tests/test_setup.py b/tests/test_setup.py
index c6126bc4a3b..1dae51966be 100644
--- a/tests/test_setup.py
+++ b/tests/test_setup.py
@@ -12,7 +12,7 @@ from homeassistant.core import callback
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_COMPONENT_LOADED)
import homeassistant.config as config_util
-from homeassistant import setup, loader
+from homeassistant import setup
import homeassistant.util.dt as dt_util
from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
@@ -20,7 +20,8 @@ from homeassistant.helpers import discovery
from tests.common import \
get_test_home_assistant, MockModule, MockPlatform, \
- assert_setup_component, get_test_config_dir
+ assert_setup_component, get_test_config_dir, mock_integration, \
+ mock_entity_platform
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
VERSION_PATH = os.path.join(get_test_config_dir(), config_util.VERSION_FILE)
@@ -50,9 +51,9 @@ class TestSetup:
'hello': str
}
}, required=True)
- loader.set_component(
+ mock_integration(
self.hass,
- 'comp_conf', MockModule('comp_conf', config_schema=config_schema))
+ MockModule('comp_conf', config_schema=config_schema))
with assert_setup_component(0):
assert not setup.setup_component(self.hass, 'comp_conf', {})
@@ -97,17 +98,15 @@ class TestSetup:
})
platform_schema_base = PLATFORM_SCHEMA_BASE.extend({
})
- loader.set_component(
+ mock_integration(
self.hass,
- 'platform_conf',
MockModule('platform_conf',
- platform_schema_base=platform_schema_base))
-
- loader.set_component(
+ platform_schema_base=platform_schema_base),
+ )
+ mock_entity_platform(
self.hass,
'platform_conf.whatever',
- MockPlatform('whatever',
- platform_schema=platform_schema))
+ MockPlatform(platform_schema=platform_schema))
with assert_setup_component(1):
assert setup.setup_component(self.hass, 'platform_conf', {
@@ -195,14 +194,13 @@ class TestSetup:
platform_schema_base = PLATFORM_SCHEMA_BASE.extend({
'hello': 'world',
})
- loader.set_component(
+ mock_integration(
self.hass,
- 'platform_conf',
MockModule('platform_conf',
platform_schema=platform_schema,
platform_schema_base=platform_schema_base))
- loader.set_component(
+ mock_entity_platform(
self.hass,
'platform_conf.whatever',
MockPlatform('whatever',
@@ -249,13 +247,12 @@ class TestSetup:
'cheers': str,
'hello': 'world',
})
- loader.set_component(
+ mock_integration(
self.hass,
- 'platform_conf',
MockModule('platform_conf',
platform_schema=component_schema))
- loader.set_component(
+ mock_entity_platform(
self.hass,
'platform_conf.whatever',
MockPlatform('whatever',
@@ -296,17 +293,15 @@ class TestSetup:
"""Test entity_namespace in PLATFORM_SCHEMA."""
component_schema = PLATFORM_SCHEMA_BASE
platform_schema = PLATFORM_SCHEMA
- loader.set_component(
+ mock_integration(
self.hass,
- 'platform_conf',
MockModule('platform_conf',
platform_schema_base=component_schema))
- loader.set_component(
+ mock_entity_platform(
self.hass,
'platform_conf.whatever',
- MockPlatform('whatever',
- platform_schema=platform_schema))
+ MockPlatform(platform_schema=platform_schema))
with assert_setup_component(1):
assert setup.setup_component(self.hass, 'platform_conf', {
@@ -322,21 +317,22 @@ class TestSetup:
def test_component_not_found(self):
"""setup_component should not crash if component doesn't exist."""
- assert not setup.setup_component(self.hass, 'non_existing')
+ assert setup.setup_component(self.hass, 'non_existing', {}) is False
def test_component_not_double_initialized(self):
"""Test we do not set up a component twice."""
mock_setup = mock.MagicMock(return_value=True)
- loader.set_component(
- self.hass, 'comp', MockModule('comp', setup=mock_setup))
+ mock_integration(
+ self.hass,
+ MockModule('comp', setup=mock_setup))
- assert setup.setup_component(self.hass, 'comp')
+ assert setup.setup_component(self.hass, 'comp', {})
assert mock_setup.called
mock_setup.reset_mock()
- assert setup.setup_component(self.hass, 'comp')
+ assert setup.setup_component(self.hass, 'comp', {})
assert not mock_setup.called
@mock.patch('homeassistant.util.package.install_package',
@@ -344,11 +340,11 @@ class TestSetup:
def test_component_not_installed_if_requirement_fails(self, mock_install):
"""Component setup should fail if requirement can't install."""
self.hass.config.skip_pip = False
- loader.set_component(
+ mock_integration(
self.hass,
- 'comp', MockModule('comp', requirements=['package==0.0.1']))
+ MockModule('comp', requirements=['package==0.0.1']))
- assert not setup.setup_component(self.hass, 'comp')
+ assert not setup.setup_component(self.hass, 'comp', {})
assert 'comp' not in self.hass.config.components
def test_component_not_setup_twice_if_loaded_during_other_setup(self):
@@ -360,17 +356,17 @@ class TestSetup:
"""Tracking Setup."""
result.append(1)
- loader.set_component(
+ mock_integration(
self.hass,
- 'comp', MockModule('comp', async_setup=async_setup))
+ MockModule('comp', async_setup=async_setup))
def setup_component():
"""Set up the component."""
- setup.setup_component(self.hass, 'comp')
+ setup.setup_component(self.hass, 'comp', {})
thread = threading.Thread(target=setup_component)
thread.start()
- setup.setup_component(self.hass, 'comp')
+ setup.setup_component(self.hass, 'comp', {})
thread.join()
@@ -378,23 +374,23 @@ class TestSetup:
def test_component_not_setup_missing_dependencies(self):
"""Test we do not set up a component if not all dependencies loaded."""
- deps = ['non_existing']
- loader.set_component(
- self.hass, 'comp', MockModule('comp', dependencies=deps))
+ deps = ['maybe_existing']
+ mock_integration(self.hass, MockModule('comp', dependencies=deps))
assert not setup.setup_component(self.hass, 'comp', {})
assert 'comp' not in self.hass.config.components
self.hass.data.pop(setup.DATA_SETUP)
- loader.set_component(
- self.hass, 'non_existing', MockModule('non_existing'))
- assert setup.setup_component(self.hass, 'comp', {})
+ mock_integration(self.hass, MockModule('comp2', dependencies=deps))
+ mock_integration(self.hass, MockModule('maybe_existing'))
+
+ assert setup.setup_component(self.hass, 'comp2', {})
def test_component_failing_setup(self):
"""Test component that fails setup."""
- loader.set_component(
- self.hass, 'comp',
+ mock_integration(
+ self.hass,
MockModule('comp', setup=lambda hass, config: False))
assert not setup.setup_component(self.hass, 'comp', {})
@@ -406,8 +402,8 @@ class TestSetup:
"""Raise exception."""
raise Exception('fail!')
- loader.set_component(
- self.hass, 'comp', MockModule('comp', setup=exception_setup))
+ mock_integration(self.hass,
+ MockModule('comp', setup=exception_setup))
assert not setup.setup_component(self.hass, 'comp', {})
assert 'comp' not in self.hass.config.components
@@ -420,12 +416,18 @@ class TestSetup:
return True
raise Exception('Config not passed in: {}'.format(config))
- loader.set_component(
- self.hass, 'comp_a',
- MockModule('comp_a', setup=config_check_setup))
+ platform = MockPlatform()
- loader.set_component(
- self.hass, 'switch.platform_a', MockPlatform('comp_b', ['comp_a']))
+ mock_integration(self.hass,
+ MockModule('comp_a', setup=config_check_setup))
+ mock_integration(
+ self.hass,
+ MockModule('platform_a',
+ setup=config_check_setup,
+ dependencies=['comp_a']),
+ )
+
+ mock_entity_platform(self.hass, 'switch.platform_a', platform)
setup.setup_component(self.hass, 'switch', {
'comp_a': {
@@ -445,7 +447,7 @@ class TestSetup:
mock_setup = mock.MagicMock(spec_set=True)
- loader.set_component(
+ mock_entity_platform(
self.hass,
'switch.platform_a',
MockPlatform(platform_schema=platform_schema,
@@ -476,7 +478,7 @@ class TestSetup:
self.hass.data.pop(setup.DATA_SETUP)
self.hass.config.components.remove('switch')
- with assert_setup_component(1):
+ with assert_setup_component(1, 'switch'):
assert setup.setup_component(self.hass, 'switch', {
'switch': {
'platform': 'platform_a',
@@ -487,35 +489,27 @@ class TestSetup:
def test_disable_component_if_invalid_return(self):
"""Test disabling component if invalid return."""
- loader.set_component(
+ mock_integration(
self.hass,
- 'disabled_component',
MockModule('disabled_component', setup=lambda hass, config: None))
- assert not setup.setup_component(self.hass, 'disabled_component')
- assert loader.get_component(self.hass, 'disabled_component') is None
+ assert not setup.setup_component(self.hass, 'disabled_component', {})
assert 'disabled_component' not in self.hass.config.components
self.hass.data.pop(setup.DATA_SETUP)
- loader.set_component(
+ mock_integration(
self.hass,
- 'disabled_component',
MockModule('disabled_component', setup=lambda hass, config: False))
- assert not setup.setup_component(self.hass, 'disabled_component')
- assert loader.get_component(
- self.hass, 'disabled_component') is not None
+ assert not setup.setup_component(self.hass, 'disabled_component', {})
assert 'disabled_component' not in self.hass.config.components
self.hass.data.pop(setup.DATA_SETUP)
- loader.set_component(
+ mock_integration(
self.hass,
- 'disabled_component',
MockModule('disabled_component', setup=lambda hass, config: True))
- assert setup.setup_component(self.hass, 'disabled_component')
- assert loader.get_component(
- self.hass, 'disabled_component') is not None
+ assert setup.setup_component(self.hass, 'disabled_component', {})
assert 'disabled_component' in self.hass.config.components
def test_all_work_done_before_start(self):
@@ -524,10 +518,10 @@ class TestSetup:
def component1_setup(hass, config):
"""Set up mock component."""
- discovery.discover(hass, 'test_component2',
- component='test_component2')
- discovery.discover(hass, 'test_component3',
- component='test_component3')
+ discovery.discover(
+ hass, 'test_component2', {}, 'test_component2', {})
+ discovery.discover(
+ hass, 'test_component3', {}, 'test_component3', {})
return True
def component_track_setup(hass, config):
@@ -535,19 +529,16 @@ class TestSetup:
call_order.append(1)
return True
- loader.set_component(
+ mock_integration(
self.hass,
- 'test_component1',
MockModule('test_component1', setup=component1_setup))
- loader.set_component(
+ mock_integration(
self.hass,
- 'test_component2',
MockModule('test_component2', setup=component_track_setup))
- loader.set_component(
+ mock_integration(
self.hass,
- 'test_component3',
MockModule('test_component3', setup=component_track_setup))
@callback
@@ -575,8 +566,7 @@ def test_component_cannot_depend_config(hass):
@asyncio.coroutine
def test_component_warn_slow_setup(hass):
"""Warn we log when a component setup takes a long time."""
- loader.set_component(
- hass, 'test_component1', MockModule('test_component1'))
+ mock_integration(hass, MockModule('test_component1'))
with mock.patch.object(hass.loop, 'call_later', mock.MagicMock()) \
as mock_call:
result = yield from setup.async_setup_component(
@@ -596,8 +586,8 @@ def test_component_warn_slow_setup(hass):
@asyncio.coroutine
def test_platform_no_warn_slow(hass):
"""Do not warn for long entity setup time."""
- loader.set_component(
- hass, 'test_component1',
+ mock_integration(
+ hass,
MockModule('test_component1', platform_schema=PLATFORM_SCHEMA))
with mock.patch.object(hass.loop, 'call_later', mock.MagicMock()) \
as mock_call:
diff --git a/tests/testing_config/__init__.py b/tests/testing_config/__init__.py
new file mode 100644
index 00000000000..98d2bc1bc8d
--- /dev/null
+++ b/tests/testing_config/__init__.py
@@ -0,0 +1 @@
+"""Configuration that's used when running tests."""
diff --git a/tests/testing_config/custom_components/__init__.py b/tests/testing_config/custom_components/__init__.py
new file mode 100644
index 00000000000..f84ba5808ae
--- /dev/null
+++ b/tests/testing_config/custom_components/__init__.py
@@ -0,0 +1 @@
+"""A collection of custom integrations used when running tests."""
diff --git a/tests/testing_config/custom_components/switch/test_embedded.py b/tests/testing_config/custom_components/switch/test_legacy.py
similarity index 100%
rename from tests/testing_config/custom_components/switch/test_embedded.py
rename to tests/testing_config/custom_components/switch/test_legacy.py
diff --git a/tests/testing_config/custom_components/switch/.translations/test.de.json b/tests/testing_config/custom_components/test/.translations/switch.de.json
similarity index 100%
rename from tests/testing_config/custom_components/switch/.translations/test.de.json
rename to tests/testing_config/custom_components/test/.translations/switch.de.json
diff --git a/tests/testing_config/custom_components/switch/.translations/test.en.json b/tests/testing_config/custom_components/test/.translations/switch.en.json
similarity index 100%
rename from tests/testing_config/custom_components/switch/.translations/test.en.json
rename to tests/testing_config/custom_components/test/.translations/switch.en.json
diff --git a/tests/testing_config/custom_components/switch/.translations/test.es.json b/tests/testing_config/custom_components/test/.translations/switch.es.json
similarity index 100%
rename from tests/testing_config/custom_components/switch/.translations/test.es.json
rename to tests/testing_config/custom_components/test/.translations/switch.es.json
diff --git a/tests/testing_config/custom_components/test/__init__.py b/tests/testing_config/custom_components/test/__init__.py
new file mode 100644
index 00000000000..206c97f847b
--- /dev/null
+++ b/tests/testing_config/custom_components/test/__init__.py
@@ -0,0 +1 @@
+"""An integration with several platforms used with unit tests."""
diff --git a/tests/testing_config/custom_components/device_tracker/test.py b/tests/testing_config/custom_components/test/device_tracker.py
similarity index 100%
rename from tests/testing_config/custom_components/device_tracker/test.py
rename to tests/testing_config/custom_components/test/device_tracker.py
diff --git a/tests/testing_config/custom_components/image_processing/test.py b/tests/testing_config/custom_components/test/image_processing.py
similarity index 100%
rename from tests/testing_config/custom_components/image_processing/test.py
rename to tests/testing_config/custom_components/test/image_processing.py
diff --git a/tests/testing_config/custom_components/light/test.py b/tests/testing_config/custom_components/test/light.py
similarity index 100%
rename from tests/testing_config/custom_components/light/test.py
rename to tests/testing_config/custom_components/test/light.py
diff --git a/tests/testing_config/custom_components/test/manifest.json b/tests/testing_config/custom_components/test/manifest.json
new file mode 100644
index 00000000000..70882fece05
--- /dev/null
+++ b/tests/testing_config/custom_components/test/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "test",
+ "name": "Test Components",
+ "documentation": "http://example.com",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/tests/testing_config/custom_components/switch/test.py b/tests/testing_config/custom_components/test/switch.py
similarity index 100%
rename from tests/testing_config/custom_components/switch/test.py
rename to tests/testing_config/custom_components/test/switch.py
diff --git a/tests/testing_config/custom_components/test_embedded/__init__.py b/tests/testing_config/custom_components/test_embedded/__init__.py
index 2206054356e..21843fc927a 100644
--- a/tests/testing_config/custom_components/test_embedded/__init__.py
+++ b/tests/testing_config/custom_components/test_embedded/__init__.py
@@ -1,4 +1,5 @@
"""Component with embedded platforms."""
+DOMAIN = 'test_embedded'
async def async_setup(hass, config):
diff --git a/tests/testing_config/custom_components/test_package/manifest.json b/tests/testing_config/custom_components/test_package/manifest.json
new file mode 100644
index 00000000000..320d2768d27
--- /dev/null
+++ b/tests/testing_config/custom_components/test_package/manifest.json
@@ -0,0 +1,8 @@
+{
+ "domain": "test_package",
+ "name": "Test Package",
+ "documentation": "http://test-package.io",
+ "requirements": [],
+ "dependencies": [],
+ "codeowners": []
+}
diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py
index 46dc3c045b2..99eee30027c 100644
--- a/tests/util/test_yaml.py
+++ b/tests/util/test_yaml.py
@@ -134,7 +134,7 @@ class TestYaml(unittest.TestCase):
def test_include_dir_named(self, mock_walk):
"""Test include dir named yaml."""
mock_walk.return_value = [
- ['/tmp', [], ['first.yaml', 'second.yaml']]
+ ['/tmp', [], ['first.yaml', 'second.yaml', 'secrets.yaml']]
]
with patch_yaml_files({
diff --git a/tox.ini b/tox.ini
index b8995d9e877..d0c4336f544 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,7 +5,7 @@ skip_missing_interpreters = True
[testenv]
basepython = {env:PYTHON3_PATH:python3}
commands =
- pytest --timeout=9 --duration=10 -qq -o console_output_style=count -p no:sugar {posargs}
+ pytest --timeout=9 --durations=10 -qq -o console_output_style=count -p no:sugar {posargs}
{toxinidir}/script/check_dirty
deps =
-r{toxinidir}/requirements_test_all.txt
@@ -13,7 +13,7 @@ deps =
[testenv:cov]
commands =
- pytest --timeout=9 --duration=10 -qq -o console_output_style=count -p no:sugar --cov --cov-report= {posargs}
+ pytest --timeout=9 --durations=10 -qq -o console_output_style=count -p no:sugar --cov --cov-report= {posargs}
{toxinidir}/script/check_dirty
deps =
-r{toxinidir}/requirements_test_all.txt
diff --git a/virtualization/Docker/Dockerfile.dev b/virtualization/Docker/Dockerfile.dev
index 90c9eee3485..4be2c382226 100644
--- a/virtualization/Docker/Dockerfile.dev
+++ b/virtualization/Docker/Dockerfile.dev
@@ -29,7 +29,7 @@ COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some dependencies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
- pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.11.3 cchardet cython
+ pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.12.2 cchardet cython
# BEGIN: Development additions