mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
commit
4bb319e658
34
.coveragerc
34
.coveragerc
@ -5,7 +5,6 @@ omit =
|
|||||||
homeassistant/__main__.py
|
homeassistant/__main__.py
|
||||||
homeassistant/helpers/signal.py
|
homeassistant/helpers/signal.py
|
||||||
homeassistant/helpers/typing.py
|
homeassistant/helpers/typing.py
|
||||||
homeassistant/monkey_patch.py
|
|
||||||
homeassistant/scripts/*.py
|
homeassistant/scripts/*.py
|
||||||
homeassistant/util/async.py
|
homeassistant/util/async.py
|
||||||
|
|
||||||
@ -85,7 +84,6 @@ omit =
|
|||||||
homeassistant/components/blockchain/sensor.py
|
homeassistant/components/blockchain/sensor.py
|
||||||
homeassistant/components/bloomsky/*
|
homeassistant/components/bloomsky/*
|
||||||
homeassistant/components/bluesound/*
|
homeassistant/components/bluesound/*
|
||||||
homeassistant/components/bluetooth_le_tracker/device_tracker.py
|
|
||||||
homeassistant/components/bluetooth_tracker/*
|
homeassistant/components/bluetooth_tracker/*
|
||||||
homeassistant/components/bme280/sensor.py
|
homeassistant/components/bme280/sensor.py
|
||||||
homeassistant/components/bme680/sensor.py
|
homeassistant/components/bme680/sensor.py
|
||||||
@ -97,6 +95,9 @@ omit =
|
|||||||
homeassistant/components/broadlink/remote.py
|
homeassistant/components/broadlink/remote.py
|
||||||
homeassistant/components/broadlink/sensor.py
|
homeassistant/components/broadlink/sensor.py
|
||||||
homeassistant/components/broadlink/switch.py
|
homeassistant/components/broadlink/switch.py
|
||||||
|
homeassistant/components/brother/__init__.py
|
||||||
|
homeassistant/components/brother/sensor.py
|
||||||
|
homeassistant/components/brother/const.py
|
||||||
homeassistant/components/brottsplatskartan/sensor.py
|
homeassistant/components/brottsplatskartan/sensor.py
|
||||||
homeassistant/components/browser/*
|
homeassistant/components/browser/*
|
||||||
homeassistant/components/brunt/cover.py
|
homeassistant/components/brunt/cover.py
|
||||||
@ -259,6 +260,9 @@ omit =
|
|||||||
homeassistant/components/geniushub/*
|
homeassistant/components/geniushub/*
|
||||||
homeassistant/components/gearbest/sensor.py
|
homeassistant/components/gearbest/sensor.py
|
||||||
homeassistant/components/geizhals/sensor.py
|
homeassistant/components/geizhals/sensor.py
|
||||||
|
homeassistant/components/gios/__init__.py
|
||||||
|
homeassistant/components/gios/air_quality.py
|
||||||
|
homeassistant/components/gios/consts.py
|
||||||
homeassistant/components/github/sensor.py
|
homeassistant/components/github/sensor.py
|
||||||
homeassistant/components/gitlab_ci/sensor.py
|
homeassistant/components/gitlab_ci/sensor.py
|
||||||
homeassistant/components/gitter/sensor.py
|
homeassistant/components/gitter/sensor.py
|
||||||
@ -319,7 +323,9 @@ omit =
|
|||||||
homeassistant/components/iaqualink/light.py
|
homeassistant/components/iaqualink/light.py
|
||||||
homeassistant/components/iaqualink/sensor.py
|
homeassistant/components/iaqualink/sensor.py
|
||||||
homeassistant/components/iaqualink/switch.py
|
homeassistant/components/iaqualink/switch.py
|
||||||
homeassistant/components/icloud/*
|
homeassistant/components/icloud/__init__.py
|
||||||
|
homeassistant/components/icloud/device_tracker.py
|
||||||
|
homeassistant/components/icloud/sensor.py
|
||||||
homeassistant/components/izone/climate.py
|
homeassistant/components/izone/climate.py
|
||||||
homeassistant/components/izone/discovery.py
|
homeassistant/components/izone/discovery.py
|
||||||
homeassistant/components/izone/__init__.py
|
homeassistant/components/izone/__init__.py
|
||||||
@ -332,6 +338,7 @@ omit =
|
|||||||
homeassistant/components/influxdb/sensor.py
|
homeassistant/components/influxdb/sensor.py
|
||||||
homeassistant/components/insteon/*
|
homeassistant/components/insteon/*
|
||||||
homeassistant/components/incomfort/*
|
homeassistant/components/incomfort/*
|
||||||
|
homeassistant/components/intesishome/*
|
||||||
homeassistant/components/ios/*
|
homeassistant/components/ios/*
|
||||||
homeassistant/components/iota/*
|
homeassistant/components/iota/*
|
||||||
homeassistant/components/iperf3/*
|
homeassistant/components/iperf3/*
|
||||||
@ -347,6 +354,7 @@ omit =
|
|||||||
homeassistant/components/kankun/switch.py
|
homeassistant/components/kankun/switch.py
|
||||||
homeassistant/components/keba/*
|
homeassistant/components/keba/*
|
||||||
homeassistant/components/keenetic_ndms2/device_tracker.py
|
homeassistant/components/keenetic_ndms2/device_tracker.py
|
||||||
|
homeassistant/components/kef/*
|
||||||
homeassistant/components/keyboard/*
|
homeassistant/components/keyboard/*
|
||||||
homeassistant/components/keyboard_remote/*
|
homeassistant/components/keyboard_remote/*
|
||||||
homeassistant/components/kira/*
|
homeassistant/components/kira/*
|
||||||
@ -601,6 +609,7 @@ omit =
|
|||||||
homeassistant/components/sensehat/light.py
|
homeassistant/components/sensehat/light.py
|
||||||
homeassistant/components/sensehat/sensor.py
|
homeassistant/components/sensehat/sensor.py
|
||||||
homeassistant/components/sensibo/climate.py
|
homeassistant/components/sensibo/climate.py
|
||||||
|
homeassistant/components/sentry/__init__.py
|
||||||
homeassistant/components/serial/sensor.py
|
homeassistant/components/serial/sensor.py
|
||||||
homeassistant/components/serial_pm/sensor.py
|
homeassistant/components/serial_pm/sensor.py
|
||||||
homeassistant/components/sesame/lock.py
|
homeassistant/components/sesame/lock.py
|
||||||
@ -610,6 +619,8 @@ omit =
|
|||||||
homeassistant/components/shodan/sensor.py
|
homeassistant/components/shodan/sensor.py
|
||||||
homeassistant/components/sht31/sensor.py
|
homeassistant/components/sht31/sensor.py
|
||||||
homeassistant/components/sigfox/sensor.py
|
homeassistant/components/sigfox/sensor.py
|
||||||
|
homeassistant/components/signal_messenger/__init__.py
|
||||||
|
homeassistant/components/signal_messenger/notify.py
|
||||||
homeassistant/components/simplepush/notify.py
|
homeassistant/components/simplepush/notify.py
|
||||||
homeassistant/components/simplisafe/__init__.py
|
homeassistant/components/simplisafe/__init__.py
|
||||||
homeassistant/components/simplisafe/alarm_control_panel.py
|
homeassistant/components/simplisafe/alarm_control_panel.py
|
||||||
@ -654,9 +665,11 @@ omit =
|
|||||||
homeassistant/components/starlingbank/sensor.py
|
homeassistant/components/starlingbank/sensor.py
|
||||||
homeassistant/components/steam_online/sensor.py
|
homeassistant/components/steam_online/sensor.py
|
||||||
homeassistant/components/stiebel_eltron/*
|
homeassistant/components/stiebel_eltron/*
|
||||||
|
homeassistant/components/stookalert/*
|
||||||
homeassistant/components/streamlabswater/*
|
homeassistant/components/streamlabswater/*
|
||||||
homeassistant/components/suez_water/*
|
homeassistant/components/suez_water/*
|
||||||
homeassistant/components/supervisord/sensor.py
|
homeassistant/components/supervisord/sensor.py
|
||||||
|
homeassistant/components/surepetcare/*.py
|
||||||
homeassistant/components/swiss_hydrological_data/sensor.py
|
homeassistant/components/swiss_hydrological_data/sensor.py
|
||||||
homeassistant/components/swiss_public_transport/sensor.py
|
homeassistant/components/swiss_public_transport/sensor.py
|
||||||
homeassistant/components/swisscom/device_tracker.py
|
homeassistant/components/swisscom/device_tracker.py
|
||||||
@ -684,7 +697,14 @@ omit =
|
|||||||
homeassistant/components/telnet/switch.py
|
homeassistant/components/telnet/switch.py
|
||||||
homeassistant/components/temper/sensor.py
|
homeassistant/components/temper/sensor.py
|
||||||
homeassistant/components/tensorflow/image_processing.py
|
homeassistant/components/tensorflow/image_processing.py
|
||||||
homeassistant/components/tesla/*
|
homeassistant/components/tesla/__init__.py
|
||||||
|
homeassistant/components/tesla/binary_sensor.py
|
||||||
|
homeassistant/components/tesla/climate.py
|
||||||
|
homeassistant/components/tesla/const.py
|
||||||
|
homeassistant/components/tesla/device_tracker.py
|
||||||
|
homeassistant/components/tesla/lock.py
|
||||||
|
homeassistant/components/tesla/sensor.py
|
||||||
|
homeassistant/components/tesla/switch.py
|
||||||
homeassistant/components/tfiac/climate.py
|
homeassistant/components/tfiac/climate.py
|
||||||
homeassistant/components/thermoworks_smoke/sensor.py
|
homeassistant/components/thermoworks_smoke/sensor.py
|
||||||
homeassistant/components/thethingsnetwork/*
|
homeassistant/components/thethingsnetwork/*
|
||||||
@ -695,6 +715,7 @@ omit =
|
|||||||
homeassistant/components/tikteck/light.py
|
homeassistant/components/tikteck/light.py
|
||||||
homeassistant/components/tile/device_tracker.py
|
homeassistant/components/tile/device_tracker.py
|
||||||
homeassistant/components/time_date/sensor.py
|
homeassistant/components/time_date/sensor.py
|
||||||
|
homeassistant/components/tmb/sensor.py
|
||||||
homeassistant/components/todoist/calendar.py
|
homeassistant/components/todoist/calendar.py
|
||||||
homeassistant/components/todoist/const.py
|
homeassistant/components/todoist/const.py
|
||||||
homeassistant/components/tof/sensor.py
|
homeassistant/components/tof/sensor.py
|
||||||
@ -704,7 +725,6 @@ omit =
|
|||||||
homeassistant/components/totalconnect/*
|
homeassistant/components/totalconnect/*
|
||||||
homeassistant/components/touchline/climate.py
|
homeassistant/components/touchline/climate.py
|
||||||
homeassistant/components/tplink/device_tracker.py
|
homeassistant/components/tplink/device_tracker.py
|
||||||
homeassistant/components/tplink/light.py
|
|
||||||
homeassistant/components/tplink/switch.py
|
homeassistant/components/tplink/switch.py
|
||||||
homeassistant/components/tplink_lte/*
|
homeassistant/components/tplink_lte/*
|
||||||
homeassistant/components/traccar/device_tracker.py
|
homeassistant/components/traccar/device_tracker.py
|
||||||
@ -745,11 +765,11 @@ omit =
|
|||||||
homeassistant/components/velbus/climate.py
|
homeassistant/components/velbus/climate.py
|
||||||
homeassistant/components/velbus/const.py
|
homeassistant/components/velbus/const.py
|
||||||
homeassistant/components/velbus/cover.py
|
homeassistant/components/velbus/cover.py
|
||||||
|
homeassistant/components/velbus/light.py
|
||||||
homeassistant/components/velbus/sensor.py
|
homeassistant/components/velbus/sensor.py
|
||||||
homeassistant/components/velbus/switch.py
|
homeassistant/components/velbus/switch.py
|
||||||
homeassistant/components/velux/*
|
homeassistant/components/velux/*
|
||||||
homeassistant/components/venstar/climate.py
|
homeassistant/components/venstar/climate.py
|
||||||
homeassistant/components/vera/*
|
|
||||||
homeassistant/components/verisure/*
|
homeassistant/components/verisure/*
|
||||||
homeassistant/components/versasense/*
|
homeassistant/components/versasense/*
|
||||||
homeassistant/components/vesync/__init__.py
|
homeassistant/components/vesync/__init__.py
|
||||||
@ -759,7 +779,7 @@ omit =
|
|||||||
homeassistant/components/viaggiatreno/sensor.py
|
homeassistant/components/viaggiatreno/sensor.py
|
||||||
homeassistant/components/vicare/*
|
homeassistant/components/vicare/*
|
||||||
homeassistant/components/vivotek/camera.py
|
homeassistant/components/vivotek/camera.py
|
||||||
homeassistant/components/vizio/media_player.py
|
homeassistant/components/vizio/*
|
||||||
homeassistant/components/vlc/media_player.py
|
homeassistant/components/vlc/media_player.py
|
||||||
homeassistant/components/vlc_telnet/media_player.py
|
homeassistant/components/vlc_telnet/media_player.py
|
||||||
homeassistant/components/volkszaehler/sensor.py
|
homeassistant/components/volkszaehler/sensor.py
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
|
|
||||||
{
|
{
|
||||||
"name": "Home Assistant Dev",
|
"name": "Home Assistant Dev",
|
||||||
"context": "..",
|
"context": "..",
|
||||||
|
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@ -3,6 +3,7 @@
|
|||||||
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
||||||
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
|
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
|
||||||
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
|
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
|
||||||
|
- Android issues should be submitted to the home-assistant-android repository: https://github.com/home-assistant/home-assistant-android/issues
|
||||||
- Do not report issues for integrations if you are using custom integration: files in <config-dir>/custom_components
|
- Do not report issues for integrations if you are using custom integration: files in <config-dir>/custom_components
|
||||||
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
||||||
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
|
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
|
||||||
|
@ -24,7 +24,7 @@ repos:
|
|||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- flake8-docstrings==1.5.0
|
- flake8-docstrings==1.5.0
|
||||||
- pydocstyle==4.0.1
|
- pydocstyle==5.0.1
|
||||||
files: ^(homeassistant|script|tests)/.+\.py$
|
files: ^(homeassistant|script|tests)/.+\.py$
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.6.2
|
rev: 1.6.2
|
||||||
@ -35,6 +35,14 @@ repos:
|
|||||||
- --format=custom
|
- --format=custom
|
||||||
- --configfile=tests/bandit.yaml
|
- --configfile=tests/bandit.yaml
|
||||||
files: ^(homeassistant|script|tests)/.+\.py$
|
files: ^(homeassistant|script|tests)/.+\.py$
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
|
rev: v4.3.21
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: check-json
|
||||||
# Using a local "system" mypy instead of the mypy hook, because its
|
# Using a local "system" mypy instead of the mypy hook, because its
|
||||||
# results depend on what is installed. And the mypy hook runs in a
|
# results depend on what is installed. And the mypy hook runs in a
|
||||||
# virtualenv of its own, meaning we'd need to install and maintain
|
# virtualenv of its own, meaning we'd need to install and maintain
|
||||||
|
@ -20,14 +20,22 @@ repos:
|
|||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- flake8-docstrings==1.5.0
|
- flake8-docstrings==1.5.0
|
||||||
- pydocstyle==4.0.1
|
- pydocstyle==5.0.1
|
||||||
files: ^(homeassistant|script|tests)/.+\.py$
|
files: ^(homeassistant|script|tests)/.+\.py$
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.6.2
|
rev: 1.6.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args:
|
args:
|
||||||
- --quiet
|
- --quiet
|
||||||
- --format=custom
|
- --format=custom
|
||||||
- --configfile=tests/bandit.yaml
|
- --configfile=tests/bandit.yaml
|
||||||
files: ^(homeassistant|script|tests)/.+\.py$
|
files: ^(homeassistant|script|tests)/.+\.py$
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
|
rev: v4.3.21
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: check-json
|
||||||
|
@ -4,7 +4,7 @@ build:
|
|||||||
image: latest
|
image: latest
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: 3.6
|
version: 3.7
|
||||||
setup_py_install: true
|
setup_py_install: true
|
||||||
|
|
||||||
requirements_file: requirements_docs.txt
|
requirements_file: requirements_docs.txt
|
||||||
|
14
.travis.yml
14
.travis.yml
@ -1,9 +1,7 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
dist: xenial
|
dist: bionic
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
|
||||||
- sourceline: "ppa:jonathonf/ffmpeg-4"
|
|
||||||
packages:
|
packages:
|
||||||
- libudev-dev
|
- libudev-dev
|
||||||
- libavformat-dev
|
- libavformat-dev
|
||||||
@ -16,15 +14,13 @@ addons:
|
|||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- python: "3.6.1"
|
- python: "3.7.0"
|
||||||
env: TOXENV=lint
|
env: TOXENV=lint
|
||||||
- python: "3.6.1"
|
- python: "3.7.0"
|
||||||
env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30
|
env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30
|
||||||
- python: "3.6.1"
|
- python: "3.7.0"
|
||||||
env: TOXENV=typing
|
env: TOXENV=typing
|
||||||
- python: "3.6.1"
|
- python: "3.7.0"
|
||||||
env: TOXENV=py36
|
|
||||||
- python: "3.7"
|
|
||||||
env: TOXENV=py37
|
env: TOXENV=py37
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
21
CODEOWNERS
21
CODEOWNERS
@ -51,6 +51,7 @@ homeassistant/components/blink/* @fronzbot
|
|||||||
homeassistant/components/bmw_connected_drive/* @gerard33
|
homeassistant/components/bmw_connected_drive/* @gerard33
|
||||||
homeassistant/components/braviatv/* @robbiet480
|
homeassistant/components/braviatv/* @robbiet480
|
||||||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||||
|
homeassistant/components/brother/* @bieniu
|
||||||
homeassistant/components/brunt/* @eavanvalkenburg
|
homeassistant/components/brunt/* @eavanvalkenburg
|
||||||
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
||||||
homeassistant/components/buienradar/* @mjj4791 @ties
|
homeassistant/components/buienradar/* @mjj4791 @ties
|
||||||
@ -85,6 +86,7 @@ homeassistant/components/ecobee/* @marthoc
|
|||||||
homeassistant/components/ecovacs/* @OverloadUT
|
homeassistant/components/ecovacs/* @OverloadUT
|
||||||
homeassistant/components/egardia/* @jeroenterheerdt
|
homeassistant/components/egardia/* @jeroenterheerdt
|
||||||
homeassistant/components/eight_sleep/* @mezz64
|
homeassistant/components/eight_sleep/* @mezz64
|
||||||
|
homeassistant/components/elgato/* @frenck
|
||||||
homeassistant/components/elv/* @majuss
|
homeassistant/components/elv/* @majuss
|
||||||
homeassistant/components/emby/* @mezz64
|
homeassistant/components/emby/* @mezz64
|
||||||
homeassistant/components/emulated_hue/* @NobleKangaroo
|
homeassistant/components/emulated_hue/* @NobleKangaroo
|
||||||
@ -118,6 +120,7 @@ homeassistant/components/geniushub/* @zxdavb
|
|||||||
homeassistant/components/geo_rss_events/* @exxamalte
|
homeassistant/components/geo_rss_events/* @exxamalte
|
||||||
homeassistant/components/geonetnz_quakes/* @exxamalte
|
homeassistant/components/geonetnz_quakes/* @exxamalte
|
||||||
homeassistant/components/geonetnz_volcano/* @exxamalte
|
homeassistant/components/geonetnz_volcano/* @exxamalte
|
||||||
|
homeassistant/components/gios/* @bieniu
|
||||||
homeassistant/components/gitter/* @fabaff
|
homeassistant/components/gitter/* @fabaff
|
||||||
homeassistant/components/glances/* @fabaff @engrbm87
|
homeassistant/components/glances/* @fabaff @engrbm87
|
||||||
homeassistant/components/gntp/* @robbiet480
|
homeassistant/components/gntp/* @robbiet480
|
||||||
@ -151,6 +154,7 @@ homeassistant/components/huawei_lte/* @scop
|
|||||||
homeassistant/components/huawei_router/* @abmantis
|
homeassistant/components/huawei_router/* @abmantis
|
||||||
homeassistant/components/hue/* @balloob
|
homeassistant/components/hue/* @balloob
|
||||||
homeassistant/components/iaqualink/* @flz
|
homeassistant/components/iaqualink/* @flz
|
||||||
|
homeassistant/components/icloud/* @Quentame
|
||||||
homeassistant/components/ign_sismologia/* @exxamalte
|
homeassistant/components/ign_sismologia/* @exxamalte
|
||||||
homeassistant/components/incomfort/* @zxdavb
|
homeassistant/components/incomfort/* @zxdavb
|
||||||
homeassistant/components/influxdb/* @fabaff
|
homeassistant/components/influxdb/* @fabaff
|
||||||
@ -161,6 +165,7 @@ homeassistant/components/input_select/* @home-assistant/core
|
|||||||
homeassistant/components/input_text/* @home-assistant/core
|
homeassistant/components/input_text/* @home-assistant/core
|
||||||
homeassistant/components/integration/* @dgomes
|
homeassistant/components/integration/* @dgomes
|
||||||
homeassistant/components/intent/* @home-assistant/core
|
homeassistant/components/intent/* @home-assistant/core
|
||||||
|
homeassistant/components/intesishome/* @jnimmo
|
||||||
homeassistant/components/ios/* @robbiet480
|
homeassistant/components/ios/* @robbiet480
|
||||||
homeassistant/components/iperf3/* @rohankapoorcom
|
homeassistant/components/iperf3/* @rohankapoorcom
|
||||||
homeassistant/components/ipma/* @dgomes
|
homeassistant/components/ipma/* @dgomes
|
||||||
@ -172,6 +177,7 @@ homeassistant/components/juicenet/* @jesserockz
|
|||||||
homeassistant/components/kaiterra/* @Michsior14
|
homeassistant/components/kaiterra/* @Michsior14
|
||||||
homeassistant/components/keba/* @dannerph
|
homeassistant/components/keba/* @dannerph
|
||||||
homeassistant/components/keenetic_ndms2/* @foxel
|
homeassistant/components/keenetic_ndms2/* @foxel
|
||||||
|
homeassistant/components/kef/* @basnijholt
|
||||||
homeassistant/components/keyboard_remote/* @bendavid
|
homeassistant/components/keyboard_remote/* @bendavid
|
||||||
homeassistant/components/knx/* @Julius2342
|
homeassistant/components/knx/* @Julius2342
|
||||||
homeassistant/components/kodi/* @armills
|
homeassistant/components/kodi/* @armills
|
||||||
@ -183,6 +189,7 @@ homeassistant/components/life360/* @pnbruckner
|
|||||||
homeassistant/components/linky/* @Quentame
|
homeassistant/components/linky/* @Quentame
|
||||||
homeassistant/components/linux_battery/* @fabaff
|
homeassistant/components/linux_battery/* @fabaff
|
||||||
homeassistant/components/liveboxplaytv/* @pschmitt
|
homeassistant/components/liveboxplaytv/* @pschmitt
|
||||||
|
homeassistant/components/local_ip/* @issacg
|
||||||
homeassistant/components/logger/* @home-assistant/core
|
homeassistant/components/logger/* @home-assistant/core
|
||||||
homeassistant/components/logi_circle/* @evanjd
|
homeassistant/components/logi_circle/* @evanjd
|
||||||
homeassistant/components/lovelace/* @home-assistant/frontend
|
homeassistant/components/lovelace/* @home-assistant/frontend
|
||||||
@ -232,6 +239,7 @@ homeassistant/components/obihai/* @dshokouhi
|
|||||||
homeassistant/components/ohmconnect/* @robbiet480
|
homeassistant/components/ohmconnect/* @robbiet480
|
||||||
homeassistant/components/ombi/* @larssont
|
homeassistant/components/ombi/* @larssont
|
||||||
homeassistant/components/onboarding/* @home-assistant/core
|
homeassistant/components/onboarding/* @home-assistant/core
|
||||||
|
homeassistant/components/onewire/* @garbled1
|
||||||
homeassistant/components/opentherm_gw/* @mvn23
|
homeassistant/components/opentherm_gw/* @mvn23
|
||||||
homeassistant/components/openuv/* @bachya
|
homeassistant/components/openuv/* @bachya
|
||||||
homeassistant/components/openweathermap/* @fabaff
|
homeassistant/components/openweathermap/* @fabaff
|
||||||
@ -244,6 +252,7 @@ homeassistant/components/pcal9535a/* @Shulyaka
|
|||||||
homeassistant/components/persistent_notification/* @home-assistant/core
|
homeassistant/components/persistent_notification/* @home-assistant/core
|
||||||
homeassistant/components/philips_js/* @elupus
|
homeassistant/components/philips_js/* @elupus
|
||||||
homeassistant/components/pi_hole/* @fabaff @johnluetke
|
homeassistant/components/pi_hole/* @fabaff @johnluetke
|
||||||
|
homeassistant/components/pilight/* @trekky12
|
||||||
homeassistant/components/plaato/* @JohNan
|
homeassistant/components/plaato/* @JohNan
|
||||||
homeassistant/components/plant/* @ChristianKuehnel
|
homeassistant/components/plant/* @ChristianKuehnel
|
||||||
homeassistant/components/plex/* @jjlawren
|
homeassistant/components/plex/* @jjlawren
|
||||||
@ -265,6 +274,7 @@ homeassistant/components/rainmachine/* @bachya
|
|||||||
homeassistant/components/random/* @fabaff
|
homeassistant/components/random/* @fabaff
|
||||||
homeassistant/components/repetier/* @MTrab
|
homeassistant/components/repetier/* @MTrab
|
||||||
homeassistant/components/rfxtrx/* @danielhiversen
|
homeassistant/components/rfxtrx/* @danielhiversen
|
||||||
|
homeassistant/components/ring/* @balloob
|
||||||
homeassistant/components/rmvtransport/* @cgtobi
|
homeassistant/components/rmvtransport/* @cgtobi
|
||||||
homeassistant/components/roomba/* @pschmitt
|
homeassistant/components/roomba/* @pschmitt
|
||||||
homeassistant/components/saj/* @fredericvl
|
homeassistant/components/saj/* @fredericvl
|
||||||
@ -274,11 +284,13 @@ homeassistant/components/scrape/* @fabaff
|
|||||||
homeassistant/components/script/* @home-assistant/core
|
homeassistant/components/script/* @home-assistant/core
|
||||||
homeassistant/components/sense/* @kbickar
|
homeassistant/components/sense/* @kbickar
|
||||||
homeassistant/components/sensibo/* @andrey-git
|
homeassistant/components/sensibo/* @andrey-git
|
||||||
|
homeassistant/components/sentry/* @dcramer
|
||||||
homeassistant/components/serial/* @fabaff
|
homeassistant/components/serial/* @fabaff
|
||||||
homeassistant/components/seventeentrack/* @bachya
|
homeassistant/components/seventeentrack/* @bachya
|
||||||
homeassistant/components/shell_command/* @home-assistant/core
|
homeassistant/components/shell_command/* @home-assistant/core
|
||||||
homeassistant/components/shiftr/* @fabaff
|
homeassistant/components/shiftr/* @fabaff
|
||||||
homeassistant/components/shodan/* @fabaff
|
homeassistant/components/shodan/* @fabaff
|
||||||
|
homeassistant/components/signal_messenger/* @bbernhard
|
||||||
homeassistant/components/simplisafe/* @bachya
|
homeassistant/components/simplisafe/* @bachya
|
||||||
homeassistant/components/sinch/* @bendikrb
|
homeassistant/components/sinch/* @bendikrb
|
||||||
homeassistant/components/slide/* @ualex73
|
homeassistant/components/slide/* @ualex73
|
||||||
@ -300,11 +312,13 @@ homeassistant/components/sql/* @dgomes
|
|||||||
homeassistant/components/starline/* @anonym-tsk
|
homeassistant/components/starline/* @anonym-tsk
|
||||||
homeassistant/components/statistics/* @fabaff
|
homeassistant/components/statistics/* @fabaff
|
||||||
homeassistant/components/stiebel_eltron/* @fucm
|
homeassistant/components/stiebel_eltron/* @fucm
|
||||||
|
homeassistant/components/stookalert/* @fwestenberg
|
||||||
homeassistant/components/stream/* @hunterjm
|
homeassistant/components/stream/* @hunterjm
|
||||||
homeassistant/components/stt/* @pvizeli
|
homeassistant/components/stt/* @pvizeli
|
||||||
homeassistant/components/suez_water/* @ooii
|
homeassistant/components/suez_water/* @ooii
|
||||||
homeassistant/components/sun/* @Swamp-Ig
|
homeassistant/components/sun/* @Swamp-Ig
|
||||||
homeassistant/components/supla/* @mwegrzynek
|
homeassistant/components/supla/* @mwegrzynek
|
||||||
|
homeassistant/components/surepetcare/* @benleb
|
||||||
homeassistant/components/swiss_hydrological_data/* @fabaff
|
homeassistant/components/swiss_hydrological_data/* @fabaff
|
||||||
homeassistant/components/swiss_public_transport/* @fabaff
|
homeassistant/components/swiss_public_transport/* @fabaff
|
||||||
homeassistant/components/switchbot/* @danielhiversen
|
homeassistant/components/switchbot/* @danielhiversen
|
||||||
@ -317,14 +331,15 @@ homeassistant/components/tado/* @michaelarnauts
|
|||||||
homeassistant/components/tahoma/* @philklei
|
homeassistant/components/tahoma/* @philklei
|
||||||
homeassistant/components/tautulli/* @ludeeus
|
homeassistant/components/tautulli/* @ludeeus
|
||||||
homeassistant/components/tellduslive/* @fredrike
|
homeassistant/components/tellduslive/* @fredrike
|
||||||
homeassistant/components/template/* @PhracturedBlue
|
homeassistant/components/template/* @PhracturedBlue @tetienne
|
||||||
homeassistant/components/tesla/* @zabuldon
|
homeassistant/components/tesla/* @zabuldon @alandtse
|
||||||
homeassistant/components/tfiac/* @fredrike @mellado
|
homeassistant/components/tfiac/* @fredrike @mellado
|
||||||
homeassistant/components/thethingsnetwork/* @fabaff
|
homeassistant/components/thethingsnetwork/* @fabaff
|
||||||
homeassistant/components/threshold/* @fabaff
|
homeassistant/components/threshold/* @fabaff
|
||||||
homeassistant/components/tibber/* @danielhiversen
|
homeassistant/components/tibber/* @danielhiversen
|
||||||
homeassistant/components/tile/* @bachya
|
homeassistant/components/tile/* @bachya
|
||||||
homeassistant/components/time_date/* @fabaff
|
homeassistant/components/time_date/* @fabaff
|
||||||
|
homeassistant/components/tmb/* @alemuro
|
||||||
homeassistant/components/todoist/* @boralyl
|
homeassistant/components/todoist/* @boralyl
|
||||||
homeassistant/components/toon/* @frenck
|
homeassistant/components/toon/* @frenck
|
||||||
homeassistant/components/tplink/* @rytilahti
|
homeassistant/components/tplink/* @rytilahti
|
||||||
@ -358,10 +373,12 @@ homeassistant/components/waqi/* @andrey-git
|
|||||||
homeassistant/components/watson_tts/* @rutkai
|
homeassistant/components/watson_tts/* @rutkai
|
||||||
homeassistant/components/weather/* @fabaff
|
homeassistant/components/weather/* @fabaff
|
||||||
homeassistant/components/weblink/* @home-assistant/core
|
homeassistant/components/weblink/* @home-assistant/core
|
||||||
|
homeassistant/components/webostv/* @bendavid
|
||||||
homeassistant/components/websocket_api/* @home-assistant/core
|
homeassistant/components/websocket_api/* @home-assistant/core
|
||||||
homeassistant/components/wemo/* @sqldiablo
|
homeassistant/components/wemo/* @sqldiablo
|
||||||
homeassistant/components/withings/* @vangorra
|
homeassistant/components/withings/* @vangorra
|
||||||
homeassistant/components/wled/* @frenck
|
homeassistant/components/wled/* @frenck
|
||||||
|
homeassistant/components/workday/* @fabaff
|
||||||
homeassistant/components/worldclock/* @fabaff
|
homeassistant/components/worldclock/* @fabaff
|
||||||
homeassistant/components/wwlln/* @bachya
|
homeassistant/components/wwlln/* @bachya
|
||||||
homeassistant/components/xbox_live/* @MartinHjelmare
|
homeassistant/components/xbox_live/* @MartinHjelmare
|
||||||
|
@ -4,7 +4,7 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
|
|||||||
|
|
||||||
The process is straight-forward.
|
The process is straight-forward.
|
||||||
|
|
||||||
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
|
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0 and 1)
|
||||||
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
|
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
|
||||||
- Write the code for your device, notification service, sensor, or IoT thing.
|
- Write the code for your device, notification service, sensor, or IoT thing.
|
||||||
- Ensure tests work.
|
- Ensure tests work.
|
||||||
@ -12,3 +12,7 @@ The process is straight-forward.
|
|||||||
|
|
||||||
Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details.
|
Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details.
|
||||||
|
|
||||||
|
## Feature suggestions
|
||||||
|
|
||||||
|
If you want to suggest a new feature for Home Assistant (e.g., new integrations), please open a thread in our [Community Forum: Feature Requests](https://community.home-assistant.io/c/feature-requests).
|
||||||
|
We use [GitHub for tracking issues](https://github.com/home-assistant/home-assistant/issues), not for tracking feature requests.
|
@ -1,14 +1,7 @@
|
|||||||
Home Assistant |Chat Status|
|
Home Assistant |Chat Status|
|
||||||
=================================================================================
|
=================================================================================
|
||||||
|
|
||||||
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
|
Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts. Perfect to run on a Raspberry Pi or a local server.
|
||||||
|
|
||||||
To get started:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
python3 -m pip install homeassistant
|
|
||||||
hass --open-ui
|
|
||||||
|
|
||||||
Check out `home-assistant.io <https://home-assistant.io>`__ for `a
|
Check out `home-assistant.io <https://home-assistant.io>`__ for `a
|
||||||
demo <https://home-assistant.io/demo/>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
|
demo <https://home-assistant.io/demo/>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
|
||||||
|
@ -14,8 +14,6 @@ pr:
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
containers:
|
containers:
|
||||||
- container: 36
|
|
||||||
image: homeassistant/ci-azure:3.6
|
|
||||||
- container: 37
|
- container: 37
|
||||||
image: homeassistant/ci-azure:3.7
|
image: homeassistant/ci-azure:3.7
|
||||||
repositories:
|
repositories:
|
||||||
@ -25,7 +23,7 @@ resources:
|
|||||||
endpoint: 'home-assistant'
|
endpoint: 'home-assistant'
|
||||||
variables:
|
variables:
|
||||||
- name: PythonMain
|
- name: PythonMain
|
||||||
value: '36'
|
value: '37'
|
||||||
- group: codecov
|
- group: codecov
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
@ -54,6 +52,14 @@ stages:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run bandit --all-files
|
pre-commit run bandit --all-files
|
||||||
displayName: 'Run bandit'
|
displayName: 'Run bandit'
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run isort --all-files --show-diff-on-failure
|
||||||
|
displayName: 'Run isort'
|
||||||
|
- script: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pre-commit run check-json --all-files
|
||||||
|
displayName: 'Run check-json'
|
||||||
- job: 'Validate'
|
- job: 'Validate'
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-latest'
|
vmImage: 'ubuntu-latest'
|
||||||
@ -91,7 +97,7 @@ stages:
|
|||||||
pre-commit install-hooks --config .pre-commit-config-all.yaml
|
pre-commit install-hooks --config .pre-commit-config-all.yaml
|
||||||
- script: |
|
- script: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run black --all-files
|
pre-commit run black --all-files --show-diff-on-failure
|
||||||
displayName: 'Check Black formatting'
|
displayName: 'Check Black formatting'
|
||||||
|
|
||||||
- stage: 'Tests'
|
- stage: 'Tests'
|
||||||
@ -104,8 +110,6 @@ stages:
|
|||||||
strategy:
|
strategy:
|
||||||
maxParallel: 3
|
maxParallel: 3
|
||||||
matrix:
|
matrix:
|
||||||
Python36:
|
|
||||||
python.container: '36'
|
|
||||||
Python37:
|
Python37:
|
||||||
python.container: '37'
|
python.container: '37'
|
||||||
container: $[ variables['python.container'] ]
|
container: $[ variables['python.container'] ]
|
||||||
|
@ -14,7 +14,7 @@ schedules:
|
|||||||
always: true
|
always: true
|
||||||
variables:
|
variables:
|
||||||
- name: versionBuilder
|
- name: versionBuilder
|
||||||
value: '6.3'
|
value: '6.9'
|
||||||
- group: docker
|
- group: docker
|
||||||
- group: github
|
- group: github
|
||||||
- group: twine
|
- group: twine
|
||||||
@ -94,7 +94,7 @@ stages:
|
|||||||
buildMachine: 'raspberrypi2,raspberrypi3,raspberrypi4,odroid-xu,tinker'
|
buildMachine: 'raspberrypi2,raspberrypi3,raspberrypi4,odroid-xu,tinker'
|
||||||
aarch64:
|
aarch64:
|
||||||
buildArch: 'aarch64'
|
buildArch: 'aarch64'
|
||||||
buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,orangepi-prime'
|
buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,odroid-n2'
|
||||||
steps:
|
steps:
|
||||||
- template: templates/azp-step-ha-version.yaml@azure
|
- template: templates/azp-step-ha-version.yaml@azure
|
||||||
- script: |
|
- script: |
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 108 KiB |
@ -8,7 +8,6 @@ Loosely based on https://github.com/astropy/astropy/pull/347
|
|||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
__licence__ = 'BSD (3 clause)'
|
__licence__ = 'BSD (3 clause)'
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
#
|
#
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import inspect
|
import inspect
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from homeassistant.const import __version__, __short_version__
|
from homeassistant.const import __short_version__, __version__
|
||||||
|
|
||||||
PROJECT_NAME = 'Home Assistant'
|
PROJECT_NAME = 'Home Assistant'
|
||||||
PROJECT_PACKAGE_NAME = 'homeassistant'
|
PROJECT_PACKAGE_NAME = 'homeassistant'
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
import argparse
|
import argparse
|
||||||
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from typing import List, Dict, Any, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any, Dict, List
|
||||||
|
|
||||||
from homeassistant import monkey_patch
|
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
||||||
from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from homeassistant import core
|
from homeassistant import core
|
||||||
@ -16,7 +16,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
def set_loop() -> None:
|
def set_loop() -> None:
|
||||||
"""Attempt to use different loop."""
|
"""Attempt to use different loop."""
|
||||||
import asyncio
|
|
||||||
from asyncio.events import BaseDefaultEventLoopPolicy
|
from asyncio.events import BaseDefaultEventLoopPolicy
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
@ -56,10 +55,8 @@ def ensure_config_path(config_dir: str) -> None:
|
|||||||
if not os.path.isdir(config_dir):
|
if not os.path.isdir(config_dir):
|
||||||
if config_dir != config_util.get_default_config_dir():
|
if config_dir != config_util.get_default_config_dir():
|
||||||
print(
|
print(
|
||||||
(
|
f"Fatal Error: Specified configuration directory {config_dir} "
|
||||||
"Fatal Error: Specified configuration directory does "
|
"does not exist"
|
||||||
"not exist {} "
|
|
||||||
).format(config_dir)
|
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -67,10 +64,8 @@ def ensure_config_path(config_dir: str) -> None:
|
|||||||
os.mkdir(config_dir)
|
os.mkdir(config_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
print(
|
print(
|
||||||
(
|
"Fatal Error: Unable to create default configuration "
|
||||||
"Fatal Error: Unable to create default configuration "
|
f"directory {config_dir}"
|
||||||
"directory {} "
|
|
||||||
).format(config_dir)
|
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -79,11 +74,7 @@ def ensure_config_path(config_dir: str) -> None:
|
|||||||
try:
|
try:
|
||||||
os.mkdir(lib_dir)
|
os.mkdir(lib_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
print(
|
print(f"Fatal Error: Unable to create library directory {lib_dir}")
|
||||||
("Fatal Error: Unable to create library " "directory {} ").format(
|
|
||||||
lib_dir
|
|
||||||
)
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -148,7 +139,7 @@ def get_arguments() -> argparse.Namespace:
|
|||||||
"--log-file",
|
"--log-file",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="Log file to write to. If not set, CONFIG/home-assistant.log " "is used",
|
help="Log file to write to. If not set, CONFIG/home-assistant.log is used",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--log-no-color", action="store_true", help="Disable color logs"
|
"--log-no-color", action="store_true", help="Disable color logs"
|
||||||
@ -217,7 +208,7 @@ def check_pid(pid_file: str) -> None:
|
|||||||
except OSError:
|
except OSError:
|
||||||
# PID does not exist
|
# PID does not exist
|
||||||
return
|
return
|
||||||
print("Fatal Error: HomeAssistant is already running.")
|
print("Fatal Error: Home Assistant is already running.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -261,7 +252,7 @@ def cmdline() -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
|
async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
|
||||||
"""Set up HASS and run."""
|
"""Set up Home Assistant and run."""
|
||||||
from homeassistant import bootstrap, core
|
from homeassistant import bootstrap, core
|
||||||
|
|
||||||
hass = core.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
@ -345,11 +336,6 @@ def main() -> int:
|
|||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
validate_python()
|
validate_python()
|
||||||
|
|
||||||
monkey_patch_needed = sys.version_info[:3] < (3, 6, 3)
|
|
||||||
if monkey_patch_needed and os.environ.get("HASS_NO_MONKEY") != "1":
|
|
||||||
monkey_patch.disable_c_asyncio()
|
|
||||||
monkey_patch.patch_weakref_tasks()
|
|
||||||
|
|
||||||
set_loop()
|
set_loop()
|
||||||
|
|
||||||
# Run a simple daemon runner process on Windows to handle restarts
|
# Run a simple daemon runner process on Windows to handle restarts
|
||||||
@ -383,13 +369,11 @@ def main() -> int:
|
|||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
write_pid(args.pid_file)
|
write_pid(args.pid_file)
|
||||||
|
|
||||||
from homeassistant.util.async_ import asyncio_run
|
exit_code = asyncio.run(setup_and_run_hass(config_dir, args))
|
||||||
|
|
||||||
exit_code = asyncio_run(setup_and_run_hass(config_dir, args))
|
|
||||||
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
||||||
try_to_restart()
|
try_to_restart()
|
||||||
|
|
||||||
return exit_code # type: ignore
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
"""Provide an authentication layer for Home Assistant."""
|
"""Provide an authentication layer for Home Assistant."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
from typing import Any, Dict, List, Optional, Tuple, cast
|
from typing import Any, Dict, List, Optional, Tuple, cast
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
||||||
from homeassistant.core import callback, HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import auth_store, models
|
from . import auth_store, models
|
||||||
from .const import GROUP_ID_ADMIN
|
from .const import GROUP_ID_ADMIN
|
||||||
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
|
from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
|
||||||
from .providers import auth_provider_from_config, AuthProvider, LoginFlow
|
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
|
||||||
|
|
||||||
EVENT_USER_ADDED = "user_added"
|
EVENT_USER_ADDED = "user_added"
|
||||||
EVENT_USER_REMOVED = "user_removed"
|
EVENT_USER_REMOVED = "user_removed"
|
||||||
@ -67,6 +67,69 @@ async def auth_manager_from_config(
|
|||||||
return manager
|
return manager
|
||||||
|
|
||||||
|
|
||||||
|
class AuthManagerFlowManager(data_entry_flow.FlowManager):
|
||||||
|
"""Manage authentication flows."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, auth_manager: "AuthManager"):
|
||||||
|
"""Init auth manager flows."""
|
||||||
|
super().__init__(hass)
|
||||||
|
self.auth_manager = auth_manager
|
||||||
|
|
||||||
|
async def async_create_flow(
|
||||||
|
self,
|
||||||
|
handler_key: Any,
|
||||||
|
*,
|
||||||
|
context: Optional[Dict[str, Any]] = None,
|
||||||
|
data: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> data_entry_flow.FlowHandler:
|
||||||
|
"""Create a login flow."""
|
||||||
|
auth_provider = self.auth_manager.get_auth_provider(*handler_key)
|
||||||
|
if not auth_provider:
|
||||||
|
raise KeyError(f"Unknown auth provider {handler_key}")
|
||||||
|
return await auth_provider.async_login_flow(context)
|
||||||
|
|
||||||
|
async def async_finish_flow(
|
||||||
|
self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Return a user as result of login flow."""
|
||||||
|
flow = cast(LoginFlow, flow)
|
||||||
|
|
||||||
|
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# we got final result
|
||||||
|
if isinstance(result["data"], models.User):
|
||||||
|
result["result"] = result["data"]
|
||||||
|
return result
|
||||||
|
|
||||||
|
auth_provider = self.auth_manager.get_auth_provider(*result["handler"])
|
||||||
|
if not auth_provider:
|
||||||
|
raise KeyError(f"Unknown auth provider {result['handler']}")
|
||||||
|
|
||||||
|
credentials = await auth_provider.async_get_or_create_credentials(
|
||||||
|
result["data"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if flow.context.get("credential_only"):
|
||||||
|
result["result"] = credentials
|
||||||
|
return result
|
||||||
|
|
||||||
|
# multi-factor module cannot enabled for new credential
|
||||||
|
# which has not linked to a user yet
|
||||||
|
if auth_provider.support_mfa and not credentials.is_new:
|
||||||
|
user = await self.auth_manager.async_get_user_by_credentials(credentials)
|
||||||
|
if user is not None:
|
||||||
|
modules = await self.auth_manager.async_get_enabled_mfa(user)
|
||||||
|
|
||||||
|
if modules:
|
||||||
|
flow.user = user
|
||||||
|
flow.available_mfa_modules = modules
|
||||||
|
return await flow.async_step_select_mfa_module()
|
||||||
|
|
||||||
|
result["result"] = await self.auth_manager.async_get_or_create_user(credentials)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class AuthManager:
|
class AuthManager:
|
||||||
"""Manage the authentication for Home Assistant."""
|
"""Manage the authentication for Home Assistant."""
|
||||||
|
|
||||||
@ -82,9 +145,7 @@ class AuthManager:
|
|||||||
self._store = store
|
self._store = store
|
||||||
self._providers = providers
|
self._providers = providers
|
||||||
self._mfa_modules = mfa_modules
|
self._mfa_modules = mfa_modules
|
||||||
self.login_flow = data_entry_flow.FlowManager(
|
self.login_flow = AuthManagerFlowManager(hass, self)
|
||||||
hass, self._async_create_login_flow, self._async_finish_login_flow
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auth_providers(self) -> List[AuthProvider]:
|
def auth_providers(self) -> List[AuthProvider]:
|
||||||
@ -417,50 +478,6 @@ class AuthManager:
|
|||||||
|
|
||||||
return refresh_token
|
return refresh_token
|
||||||
|
|
||||||
async def _async_create_login_flow(
|
|
||||||
self, handler: _ProviderKey, *, context: Optional[Dict], data: Optional[Any]
|
|
||||||
) -> data_entry_flow.FlowHandler:
|
|
||||||
"""Create a login flow."""
|
|
||||||
auth_provider = self._providers[handler]
|
|
||||||
|
|
||||||
return await auth_provider.async_login_flow(context)
|
|
||||||
|
|
||||||
async def _async_finish_login_flow(
|
|
||||||
self, flow: LoginFlow, result: Dict[str, Any]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Return a user as result of login flow."""
|
|
||||||
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
|
||||||
return result
|
|
||||||
|
|
||||||
# we got final result
|
|
||||||
if isinstance(result["data"], models.User):
|
|
||||||
result["result"] = result["data"]
|
|
||||||
return result
|
|
||||||
|
|
||||||
auth_provider = self._providers[result["handler"]]
|
|
||||||
credentials = await auth_provider.async_get_or_create_credentials(
|
|
||||||
result["data"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if flow.context.get("credential_only"):
|
|
||||||
result["result"] = credentials
|
|
||||||
return result
|
|
||||||
|
|
||||||
# multi-factor module cannot enabled for new credential
|
|
||||||
# which has not linked to a user yet
|
|
||||||
if auth_provider.support_mfa and not credentials.is_new:
|
|
||||||
user = await self.async_get_user_by_credentials(credentials)
|
|
||||||
if user is not None:
|
|
||||||
modules = await self.async_get_enabled_mfa(user)
|
|
||||||
|
|
||||||
if modules:
|
|
||||||
flow.user = user
|
|
||||||
flow.available_mfa_modules = modules
|
|
||||||
return await flow.async_step_select_mfa_module()
|
|
||||||
|
|
||||||
result["result"] = await self.async_get_or_create_user(credentials)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_get_auth_provider(
|
def _async_get_auth_provider(
|
||||||
self, credentials: models.Credentials
|
self, credentials: models.Credentials
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from .const import GROUP_ID_ADMIN, GROUP_ID_USER, GROUP_ID_READ_ONLY
|
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
|
||||||
from .permissions import PermissionLookup, system_policies
|
from .permissions import PermissionLookup, system_policies
|
||||||
from .permissions.types import PolicyType
|
from .permissions.types import PolicyType
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from typing import Any, Dict, Optional
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from homeassistant import requirements, data_entry_flow
|
from homeassistant import data_entry_flow, requirements
|
||||||
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
@ -7,9 +7,9 @@ import voluptuous as vol
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
MultiFactorAuthModule,
|
|
||||||
MULTI_FACTOR_AUTH_MODULES,
|
|
||||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
||||||
|
MULTI_FACTOR_AUTH_MODULES,
|
||||||
|
MultiFactorAuthModule,
|
||||||
SetupFlow,
|
SetupFlow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
Sending HOTP through notify service
|
Sending HOTP through notify service
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Any, Dict, Optional, List
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -16,9 +16,9 @@ from homeassistant.exceptions import ServiceNotFound
|
|||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
MultiFactorAuthModule,
|
|
||||||
MULTI_FACTOR_AUTH_MODULES,
|
|
||||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
||||||
|
MULTI_FACTOR_AUTH_MODULES,
|
||||||
|
MultiFactorAuthModule,
|
||||||
SetupFlow,
|
SetupFlow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Time-based One Time Password auth module."""
|
"""Time-based One Time Password auth module."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import logging
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any, Dict, Optional, Tuple
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -10,9 +10,9 @@ from homeassistant.auth.models import User
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
MultiFactorAuthModule,
|
|
||||||
MULTI_FACTOR_AUTH_MODULES,
|
|
||||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
||||||
|
MULTI_FACTOR_AUTH_MODULES,
|
||||||
|
MultiFactorAuthModule,
|
||||||
SetupFlow,
|
SetupFlow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Auth models."""
|
"""Auth models."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import secrets
|
||||||
from typing import Dict, List, NamedTuple, Optional
|
from typing import Dict, List, NamedTuple, Optional
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -9,7 +10,6 @@ from homeassistant.util import dt as dt_util
|
|||||||
|
|
||||||
from . import permissions as perm_mdl
|
from . import permissions as perm_mdl
|
||||||
from .const import GROUP_ID_ADMIN
|
from .const import GROUP_ID_ADMIN
|
||||||
from .util import generate_secret
|
|
||||||
|
|
||||||
TOKEN_TYPE_NORMAL = "normal"
|
TOKEN_TYPE_NORMAL = "normal"
|
||||||
TOKEN_TYPE_SYSTEM = "system"
|
TOKEN_TYPE_SYSTEM = "system"
|
||||||
@ -96,8 +96,8 @@ class RefreshToken:
|
|||||||
)
|
)
|
||||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||||
created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
|
created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
|
||||||
token = attr.ib(type=str, factory=lambda: generate_secret(64))
|
token = attr.ib(type=str, factory=lambda: secrets.token_hex(64))
|
||||||
jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64))
|
jwt_key = attr.ib(type=str, factory=lambda: secrets.token_hex(64))
|
||||||
|
|
||||||
last_used_at = attr.ib(type=Optional[datetime], default=None)
|
last_used_at = attr.ib(type=Optional[datetime], default=None)
|
||||||
last_used_ip = attr.ib(type=Optional[str], default=None)
|
last_used_ip = attr.ib(type=Optional[str], default=None)
|
||||||
|
@ -5,13 +5,12 @@ from typing import Any, Callable, Optional
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .const import CAT_ENTITIES
|
from .const import CAT_ENTITIES
|
||||||
from .models import PermissionLookup
|
|
||||||
from .types import PolicyType
|
|
||||||
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
|
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
|
||||||
from .merge import merge_policies # noqa: F401
|
from .merge import merge_policies # noqa: F401
|
||||||
|
from .models import PermissionLookup
|
||||||
|
from .types import PolicyType
|
||||||
from .util import test_all
|
from .util import test_all
|
||||||
|
|
||||||
|
|
||||||
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
|
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -4,11 +4,10 @@ from typing import Callable, Optional
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT
|
from .const import POLICY_CONTROL, POLICY_EDIT, POLICY_READ, SUBCAT_ALL
|
||||||
from .models import PermissionLookup
|
from .models import PermissionLookup
|
||||||
from .types import CategoryType, SubCategoryDict, ValueType
|
from .types import CategoryType, SubCategoryDict, ValueType
|
||||||
|
from .util import SubCatLookupType, compile_policy, lookup_all
|
||||||
from .util import SubCatLookupType, lookup_all, compile_policy
|
|
||||||
|
|
||||||
SINGLE_ENTITY_SCHEMA = vol.Any(
|
SINGLE_ENTITY_SCHEMA = vol.Any(
|
||||||
True,
|
True,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Merging of policies."""
|
"""Merging of policies."""
|
||||||
from typing import cast, Dict, List, Set
|
from typing import Dict, List, Set, cast
|
||||||
|
|
||||||
from .types import PolicyType, CategoryType
|
from .types import CategoryType, PolicyType
|
||||||
|
|
||||||
|
|
||||||
def merge_policies(policies: List[PolicyType]) -> PolicyType:
|
def merge_policies(policies: List[PolicyType]) -> PolicyType:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""System policies."""
|
"""System policies."""
|
||||||
from .const import CAT_ENTITIES, SUBCAT_ALL, POLICY_READ
|
from .const import CAT_ENTITIES, POLICY_READ, SUBCAT_ALL
|
||||||
|
|
||||||
ADMIN_POLICY = {CAT_ENTITIES: True}
|
ADMIN_POLICY = {CAT_ENTITIES: True}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""Helpers to deal with permissions."""
|
"""Helpers to deal with permissions."""
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from typing import Callable, Dict, List, Optional, cast
|
from typing import Callable, Dict, List, Optional, cast
|
||||||
|
|
||||||
from .const import SUBCAT_ALL
|
from .const import SUBCAT_ALL
|
||||||
|
@ -8,8 +8,8 @@ import voluptuous as vol
|
|||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from homeassistant import data_entry_flow, requirements
|
from homeassistant import data_entry_flow, requirements
|
||||||
from homeassistant.core import callback, HomeAssistant
|
|
||||||
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
"""Auth provider that validates credentials via an external command."""
|
"""Auth provider that validates credentials via an external command."""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional, cast
|
|
||||||
|
|
||||||
import asyncio.subprocess
|
import asyncio.subprocess
|
||||||
import collections
|
import collections
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Any, Dict, Optional, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
|
||||||
|
|
||||||
CONF_COMMAND = "command"
|
CONF_COMMAND = "command"
|
||||||
CONF_ARGS = "args"
|
CONF_ARGS = "args"
|
||||||
CONF_META = "meta"
|
CONF_META = "meta"
|
||||||
@ -78,7 +76,7 @@ class CommandLineAuthProvider(AuthProvider):
|
|||||||
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"User %r failed to authenticate, command exited " "with code %d.",
|
"User %r failed to authenticate, command exited with code %d.",
|
||||||
username,
|
username,
|
||||||
process.returncode,
|
process.returncode,
|
||||||
)
|
)
|
||||||
|
@ -3,21 +3,18 @@ import asyncio
|
|||||||
import base64
|
import base64
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional, Set, cast
|
from typing import Any, Dict, List, Optional, Set, cast
|
||||||
|
|
||||||
import bcrypt
|
import bcrypt
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_ID
|
from homeassistant.const import CONF_ID
|
||||||
from homeassistant.core import callback, HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
|
||||||
|
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
STORAGE_KEY = "auth_provider.homeassistant"
|
STORAGE_KEY = "auth_provider.homeassistant"
|
||||||
|
|
||||||
@ -203,7 +200,7 @@ class Data:
|
|||||||
|
|
||||||
@AUTH_PROVIDERS.register("homeassistant")
|
@AUTH_PROVIDERS.register("homeassistant")
|
||||||
class HassAuthProvider(AuthProvider):
|
class HassAuthProvider(AuthProvider):
|
||||||
"""Auth provider based on a local storage of users in HASS config dir."""
|
"""Auth provider based on a local storage of users in Home Assistant config dir."""
|
||||||
|
|
||||||
DEFAULT_TITLE = "Home Assistant Local"
|
DEFAULT_TITLE = "Home Assistant Local"
|
||||||
|
|
||||||
|
@ -5,13 +5,12 @@ from typing import Any, Dict, Optional, cast
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
|
||||||
|
|
||||||
USER_SCHEMA = vol.Schema(
|
USER_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("username"): str,
|
vol.Required("username"): str,
|
||||||
|
@ -12,9 +12,9 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
from .. import AuthManager
|
from .. import AuthManager
|
||||||
from ..models import Credentials, UserMeta, User
|
from ..models import Credentials, User, UserMeta
|
||||||
|
|
||||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||||
CONF_API_PASSWORD = "api_password"
|
CONF_API_PASSWORD = "api_password"
|
||||||
|
@ -3,15 +3,16 @@
|
|||||||
It shows list of users if access from trusted network.
|
It shows list of users if access from trusted network.
|
||||||
Abort login flow if not access from trusted network.
|
Abort login flow if not access from trusted network.
|
||||||
"""
|
"""
|
||||||
from ipaddress import ip_network, IPv4Address, IPv6Address, IPv4Network, IPv6Network
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network
|
||||||
from typing import Any, Dict, List, Optional, Union, cast
|
from typing import Any, Dict, List, Optional, Union, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import Credentials, UserMeta
|
||||||
|
|
||||||
IPAddress = Union[IPv4Address, IPv6Address]
|
IPAddress = Union[IPv4Address, IPv6Address]
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
"""Auth utils."""
|
|
||||||
import binascii
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def generate_secret(entropy: int = 32) -> str:
|
|
||||||
"""Generate a secret.
|
|
||||||
|
|
||||||
Backport of secrets.token_hex from Python 3.6
|
|
||||||
|
|
||||||
Event loop friendly.
|
|
||||||
"""
|
|
||||||
return binascii.hexlify(os.urandom(entropy)).decode("ascii")
|
|
@ -1,22 +1,26 @@
|
|||||||
"""Provide methods to bootstrap a Home Assistant instance."""
|
"""Provide methods to bootstrap a Home Assistant instance."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections import OrderedDict
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from time import time
|
from time import time
|
||||||
from collections import OrderedDict
|
from typing import Any, Dict, Optional, Set
|
||||||
from typing import Any, Optional, Dict, Set
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import core, config as conf_util, config_entries, loader
|
from homeassistant import config as conf_util, config_entries, core, loader
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
from homeassistant.const import (
|
||||||
|
EVENT_HOMEASSISTANT_CLOSE,
|
||||||
|
REQUIRED_NEXT_PYTHON_DATE,
|
||||||
|
REQUIRED_NEXT_PYTHON_VER,
|
||||||
|
)
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util.logging import AsyncHandler
|
from homeassistant.util.logging import AsyncHandler
|
||||||
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
||||||
from homeassistant.util.yaml import clear_secret_cache
|
from homeassistant.util.yaml import clear_secret_cache
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -27,7 +31,7 @@ DATA_LOGGING = "logging"
|
|||||||
|
|
||||||
DEBUGGER_INTEGRATIONS = {"ptvsd"}
|
DEBUGGER_INTEGRATIONS = {"ptvsd"}
|
||||||
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
|
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
|
||||||
LOGGING_INTEGRATIONS = {"logger", "system_log"}
|
LOGGING_INTEGRATIONS = {"logger", "system_log", "sentry"}
|
||||||
STAGE_1_INTEGRATIONS = {
|
STAGE_1_INTEGRATIONS = {
|
||||||
# To record data
|
# To record data
|
||||||
"recorder",
|
"recorder",
|
||||||
@ -62,7 +66,7 @@ async def async_from_config_dict(
|
|||||||
hass.config.skip_pip = skip_pip
|
hass.config.skip_pip = skip_pip
|
||||||
if skip_pip:
|
if skip_pip:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Skipping pip installation of required modules. " "This may cause issues"
|
"Skipping pip installation of required modules. This may cause issues"
|
||||||
)
|
)
|
||||||
|
|
||||||
core_config = config.get(core.DOMAIN, {})
|
core_config = config.get(core.DOMAIN, {})
|
||||||
@ -95,11 +99,14 @@ async def async_from_config_dict(
|
|||||||
stop = time()
|
stop = time()
|
||||||
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
|
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
|
||||||
|
|
||||||
if sys.version_info[:3] < (3, 7, 0):
|
if REQUIRED_NEXT_PYTHON_DATE and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER:
|
||||||
msg = (
|
msg = (
|
||||||
"Python 3.6 support is deprecated and will "
|
"Support for the running Python version "
|
||||||
"be removed in the first release after December 15, 2019. Please "
|
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
|
||||||
"upgrade Python to 3.7.0 or higher."
|
f"be removed in the first release after {REQUIRED_NEXT_PYTHON_DATE}. "
|
||||||
|
"Please upgrade Python to "
|
||||||
|
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
|
||||||
|
"higher."
|
||||||
)
|
)
|
||||||
_LOGGER.warning(msg)
|
_LOGGER.warning(msg)
|
||||||
hass.components.persistent_notification.async_create(
|
hass.components.persistent_notification.async_create(
|
||||||
@ -161,7 +168,7 @@ def async_enable_logging(
|
|||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
fmt = "%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s"
|
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||||
datefmt = "%Y-%m-%d %H:%M:%S"
|
datefmt = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
if not log_no_color:
|
if not log_no_color:
|
||||||
|
@ -11,7 +11,6 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
|
|
||||||
|
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"password": "Adgangskode",
|
"password": "Adgangskode",
|
||||||
"username": "Email adresse"
|
"username": "Email-adresse"
|
||||||
},
|
},
|
||||||
"title": "Udfyld dine Abode-loginoplysninger"
|
"title": "Udfyld dine Abode-loginoplysninger"
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,14 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
DOMAIN,
|
|
||||||
DEFAULT_CACHEDB,
|
DEFAULT_CACHEDB,
|
||||||
|
DOMAIN,
|
||||||
SIGNAL_CAPTURE_IMAGE,
|
SIGNAL_CAPTURE_IMAGE,
|
||||||
SIGNAL_TRIGGER_QUICK_ACTION,
|
SIGNAL_TRIGGER_QUICK_ACTION,
|
||||||
)
|
)
|
||||||
@ -170,7 +170,7 @@ async def async_unload_entry(hass, config_entry):
|
|||||||
|
|
||||||
|
|
||||||
def setup_hass_services(hass):
|
def setup_hass_services(hass):
|
||||||
"""Home assistant services."""
|
"""Home Assistant services."""
|
||||||
|
|
||||||
def change_setting(call):
|
def change_setting(call):
|
||||||
"""Change an Abode system setting."""
|
"""Change an Abode system setting."""
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant import config_entries
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
from .const import DOMAIN, DEFAULT_CACHEDB # pylint: disable=unused-import
|
from .const import DEFAULT_CACHEDB, DOMAIN # pylint: disable=unused-import
|
||||||
|
|
||||||
CONF_POLLING = "polling"
|
CONF_POLLING = "polling"
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class AbodeLight(AbodeDevice, Light):
|
|||||||
self._device.set_color(kwargs[ATTR_HS_COLOR])
|
self._device.set_color(kwargs[ATTR_HS_COLOR])
|
||||||
|
|
||||||
if ATTR_BRIGHTNESS in kwargs and self._device.is_dimmable:
|
if ATTR_BRIGHTNESS in kwargs and self._device.is_dimmable:
|
||||||
# Convert HASS brightness (0-255) to Abode brightness (0-99)
|
# Convert Home Assistant brightness (0-255) to Abode brightness (0-99)
|
||||||
# If 100 is sent to Abode, response is 99 causing an error
|
# If 100 is sent to Abode, response is 99 causing an error
|
||||||
self._device.set_level(ceil(kwargs[ATTR_BRIGHTNESS] * 99 / 255.0))
|
self._device.set_level(ceil(kwargs[ATTR_BRIGHTNESS] * 99 / 255.0))
|
||||||
else:
|
else:
|
||||||
@ -78,7 +78,7 @@ class AbodeLight(AbodeDevice, Light):
|
|||||||
# Abode returns 100 during device initialization and device refresh
|
# Abode returns 100 during device initialization and device refresh
|
||||||
if brightness == 100:
|
if brightness == 100:
|
||||||
return 255
|
return 255
|
||||||
# Convert Abode brightness (0-99) to HASS brightness (0-255)
|
# Convert Abode brightness (0-99) to Home Assistant brightness (0-255)
|
||||||
return ceil(brightness * 255 / 99.0)
|
return ceil(brightness * 255 / 99.0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -3,11 +3,7 @@
|
|||||||
"name": "Abode",
|
"name": "Abode",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||||
"requirements": [
|
"requirements": ["abodepy==0.16.7"],
|
||||||
"abodepy==0.16.7"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": [
|
"codeowners": ["@shred86"]
|
||||||
"@shred86"
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "acer_projector",
|
"domain": "acer_projector",
|
||||||
"name": "Acer projector",
|
"name": "Acer Projector",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
||||||
"requirements": [
|
"requirements": ["pyserial==3.1.1"],
|
||||||
"pyserial==3.1.1"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import serial
|
|
||||||
|
|
||||||
|
import serial
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
|
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON,
|
|
||||||
STATE_OFF,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
CONF_NAME,
|
|
||||||
CONF_FILENAME,
|
CONF_FILENAME,
|
||||||
|
CONF_NAME,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
||||||
|
from collections import namedtuple
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import telnetlib
|
import telnetlib
|
||||||
from collections import namedtuple
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
from homeassistant.components.device_tracker import (
|
from homeassistant.components.device_tracker import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
DeviceScanner,
|
DeviceScanner,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"adguard_home_addon_outdated": "Denne integration kr\u00e6ver AdGuard Home {minimal_version} eller h\u00f8jere, du har {current_version}. Opdater venligst din Hass.io AdGuard Home-tilf\u00f8jelse.",
|
||||||
|
"adguard_home_outdated": "Denne integration kr\u00e6ver AdGuard Home {minimal_version} eller h\u00f8jere, du har {current_version}.",
|
||||||
"existing_instance_updated": "Opdaterede eksisterende konfiguration.",
|
"existing_instance_updated": "Opdaterede eksisterende konfiguration.",
|
||||||
"single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af AdGuard Home."
|
"single_instance_allowed": "Kun en enkelt konfiguration af AdGuard Home er tilladt."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"connection_error": "Forbindelse mislykkedes."
|
"connection_error": "Forbindelse mislykkedes."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"hassio_confirm": {
|
"hassio_confirm": {
|
||||||
"description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home, der leveres af Hass.io add-on: {addon}?",
|
"description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Hass.io-tilf\u00f8jelsen: {addon}?",
|
||||||
"title": "AdGuard Home via Hass.io add-on"
|
"title": "AdGuard Home via Hass.io-tilf\u00f8jelse"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
@ -21,8 +23,8 @@
|
|||||||
"username": "Brugernavn",
|
"username": "Brugernavn",
|
||||||
"verify_ssl": "AdGuard Home bruger et korrekt certifikat"
|
"verify_ssl": "AdGuard Home bruger et korrekt certifikat"
|
||||||
},
|
},
|
||||||
"description": "Konfigurer din AdGuard Home instans for at tillade overv\u00e5gning og kontrol.",
|
"description": "Konfigurer din AdGuard Home-instans for at tillade overv\u00e5gning og kontrol.",
|
||||||
"title": "Link AdGuard Home."
|
"title": "Forbind din AdGuard Home."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "AdGuard Home"
|
"title": "AdGuard Home"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"adguard_home_addon_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}. Bitte aktualisieren Sie Ihr Hass.io AdGuard Home Add-on.",
|
||||||
|
"adguard_home_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}.",
|
||||||
"existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.",
|
"existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.",
|
||||||
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig."
|
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig."
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo Hass.io AdGuard Home.",
|
"adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo AdGuard Home di Hass.io.",
|
||||||
"adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.",
|
"adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.",
|
||||||
"existing_instance_updated": "Configurazione esistente aggiornata.",
|
"existing_instance_updated": "Configurazione esistente aggiornata.",
|
||||||
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
|
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
|
||||||
@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"hassio_confirm": {
|
"hassio_confirm": {
|
||||||
"description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon} ?",
|
"description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon}?",
|
||||||
"title": "AdGuard Home tramite il componente aggiuntivo di Hass.io"
|
"title": "AdGuard Home tramite il componente aggiuntivo di Hass.io"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"hassio_confirm": {
|
"hassio_confirm": {
|
||||||
"description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c AdGuard Home \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
|
"description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c AdGuard Home \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
|
||||||
"title": "Hass.io \uc560\ub4dc\uc628\uc758 AdGuard Home"
|
"title": "Hass.io \uc560\ub4dc\uc628\uc758 AdGuard Home"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -61,7 +61,6 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
|||||||
password=entry.data[CONF_PASSWORD],
|
password=entry.data[CONF_PASSWORD],
|
||||||
tls=entry.data[CONF_SSL],
|
tls=entry.data[CONF_SSL],
|
||||||
verify_ssl=entry.data[CONF_VERIFY_SSL],
|
verify_ssl=entry.data[CONF_VERIFY_SSL],
|
||||||
loop=hass.loop,
|
|
||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -79,7 +79,6 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
|||||||
password=user_input.get(CONF_PASSWORD),
|
password=user_input.get(CONF_PASSWORD),
|
||||||
tls=user_input[CONF_SSL],
|
tls=user_input[CONF_SSL],
|
||||||
verify_ssl=user_input[CONF_VERIFY_SSL],
|
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||||
loop=self.hass.loop,
|
|
||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -161,7 +160,6 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
|||||||
self._hassio_discovery[CONF_HOST],
|
self._hassio_discovery[CONF_HOST],
|
||||||
port=self._hassio_discovery[CONF_PORT],
|
port=self._hassio_discovery[CONF_PORT],
|
||||||
tls=False,
|
tls=False,
|
||||||
loop=self.hass.loop,
|
|
||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
{
|
{
|
||||||
"domain": "adguard",
|
"domain": "adguard",
|
||||||
"name": "AdGuard Home",
|
"name": "AdGuard Home",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/adguard",
|
"documentation": "https://www.home-assistant.io/integrations/adguard",
|
||||||
"requirements": [
|
"requirements": ["adguardhome==0.4.0"],
|
||||||
"adguardhome==0.3.0"
|
"dependencies": [],
|
||||||
],
|
"codeowners": ["@frenck"]
|
||||||
"dependencies": [],
|
}
|
||||||
"codeowners": [
|
|
||||||
"@frenck"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -178,7 +178,7 @@ class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
|
|||||||
"""Initialize AdGuard Home sensor."""
|
"""Initialize AdGuard Home sensor."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
adguard,
|
adguard,
|
||||||
"Searches Safe Search Enforced",
|
"AdGuard Safe Searches Enforced",
|
||||||
"mdi:shield-search",
|
"mdi:shield-search",
|
||||||
"enforced_safesearch",
|
"enforced_safesearch",
|
||||||
"requests",
|
"requests",
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
"""Support for Automation Device Specification (ADS)."""
|
"""Support for Automation Device Specification (ADS)."""
|
||||||
import threading
|
|
||||||
import struct
|
|
||||||
import logging
|
|
||||||
import ctypes
|
|
||||||
from collections import namedtuple
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections import namedtuple
|
||||||
|
import ctypes
|
||||||
|
import logging
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
import pyads
|
import pyads
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import (
|
|||||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE
|
from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -4,25 +4,25 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
PLATFORM_SCHEMA,
|
|
||||||
SUPPORT_OPEN,
|
|
||||||
SUPPORT_CLOSE,
|
|
||||||
SUPPORT_STOP,
|
|
||||||
SUPPORT_SET_POSITION,
|
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
DEVICE_CLASSES_SCHEMA,
|
DEVICE_CLASSES_SCHEMA,
|
||||||
|
PLATFORM_SCHEMA,
|
||||||
|
SUPPORT_CLOSE,
|
||||||
|
SUPPORT_OPEN,
|
||||||
|
SUPPORT_SET_POSITION,
|
||||||
|
SUPPORT_STOP,
|
||||||
CoverDevice,
|
CoverDevice,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS
|
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
CONF_ADS_VAR,
|
CONF_ADS_VAR,
|
||||||
CONF_ADS_VAR_POSITION,
|
CONF_ADS_VAR_POSITION,
|
||||||
DATA_ADS,
|
DATA_ADS,
|
||||||
AdsEntity,
|
|
||||||
STATE_KEY_STATE,
|
|
||||||
STATE_KEY_POSITION,
|
STATE_KEY_POSITION,
|
||||||
|
STATE_KEY_STATE,
|
||||||
|
AdsEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -16,9 +16,9 @@ from . import (
|
|||||||
CONF_ADS_VAR,
|
CONF_ADS_VAR,
|
||||||
CONF_ADS_VAR_BRIGHTNESS,
|
CONF_ADS_VAR_BRIGHTNESS,
|
||||||
DATA_ADS,
|
DATA_ADS,
|
||||||
AdsEntity,
|
|
||||||
STATE_KEY_BRIGHTNESS,
|
STATE_KEY_BRIGHTNESS,
|
||||||
STATE_KEY_STATE,
|
STATE_KEY_STATE,
|
||||||
|
AdsEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "ads",
|
"domain": "ads",
|
||||||
"name": "Ads",
|
"name": "ADS",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ads",
|
"documentation": "https://www.home-assistant.io/integrations/ads",
|
||||||
"requirements": [
|
"requirements": ["pyads==3.0.7"],
|
||||||
"pyads==3.0.7"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
|
|||||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, AdsEntity, STATE_KEY_STATE
|
from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, STATE_KEY_STATE, AdsEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
|
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE
|
from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "aftership",
|
"domain": "aftership",
|
||||||
"name": "Aftership",
|
"name": "AfterShip",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/aftership",
|
"documentation": "https://www.home-assistant.io/integrations/aftership",
|
||||||
"requirements": [
|
"requirements": ["pyaftership==0.1.2"],
|
||||||
"pyaftership==0.1.2"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
async_add_entities([instance], True)
|
async_add_entities([instance], True)
|
||||||
|
|
||||||
async def handle_add_tracking(call):
|
async def handle_add_tracking(call):
|
||||||
"""Call when a user adds a new Aftership tracking from HASS."""
|
"""Call when a user adds a new Aftership tracking from Home Assistant."""
|
||||||
title = call.data.get(CONF_TITLE)
|
title = call.data.get(CONF_TITLE)
|
||||||
slug = call.data.get(CONF_SLUG)
|
slug = call.data.get(CONF_SLUG)
|
||||||
tracking_number = call.data[CONF_TRACKING_NUMBER]
|
tracking_number = call.data[CONF_TRACKING_NUMBER]
|
||||||
@ -93,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def handle_remove_tracking(call):
|
async def handle_remove_tracking(call):
|
||||||
"""Call when a user removes an Aftership tracking from HASS."""
|
"""Call when a user removes an Aftership tracking from Home Assistant."""
|
||||||
slug = call.data[CONF_SLUG]
|
slug = call.data[CONF_SLUG]
|
||||||
tracking_number = call.data[CONF_TRACKING_NUMBER]
|
tracking_number = call.data[CONF_TRACKING_NUMBER]
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
|
||||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
PLATFORM_SCHEMA_BASE,
|
PLATFORM_SCHEMA_BASE,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"domain": "air_quality",
|
"domain": "air_quality",
|
||||||
"name": "Air quality",
|
"name": "Air Quality",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/air_quality",
|
"documentation": "https://www.home-assistant.io/integrations/air_quality",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"auth": "API-n\u00f8glen er ikke korrekt.",
|
"auth": "API-n\u00f8glen er ikke korrekt.",
|
||||||
"name_exists": "Navnet findes allerede.",
|
"name_exists": "Navnet findes allerede.",
|
||||||
"wrong_location": "Ingen Airly m\u00e5lestationer i dette omr\u00e5de."
|
"wrong_location": "Ingen Airly-m\u00e5lestationer i dette omr\u00e5de."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
@ -13,7 +13,7 @@
|
|||||||
"longitude": "L\u00e6ngdegrad",
|
"longitude": "L\u00e6ngdegrad",
|
||||||
"name": "Integrationens navn"
|
"name": "Integrationens navn"
|
||||||
},
|
},
|
||||||
"description": "Konfigurer Airly luftkvalitet integration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register",
|
"description": "Konfigurer Airly luftkvalitetsintegration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register",
|
||||||
"title": "Airly"
|
"title": "Airly"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -10,7 +10,6 @@ import async_timeout
|
|||||||
|
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||||
from homeassistant.core import Config, HomeAssistant
|
from homeassistant.core import Config, HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
@ -48,9 +47,6 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
|
|
||||||
await airly.async_update()
|
await airly.async_update()
|
||||||
|
|
||||||
if not airly.data:
|
|
||||||
raise ConfigEntryNotReady()
|
|
||||||
|
|
||||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly
|
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"""Support for the Airly air_quality service."""
|
"""Support for the Airly air_quality service."""
|
||||||
from homeassistant.components.air_quality import (
|
from homeassistant.components.air_quality import (
|
||||||
AirQualityEntity,
|
|
||||||
ATTR_AQI,
|
ATTR_AQI,
|
||||||
ATTR_PM_10,
|
|
||||||
ATTR_PM_2_5,
|
ATTR_PM_2_5,
|
||||||
|
ATTR_PM_10,
|
||||||
|
AirQualityEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_API_ADVICE,
|
ATTR_API_ADVICE,
|
||||||
@ -35,10 +35,13 @@ LABEL_PM_10_PERCENT = f"{ATTR_PM_10}_percent_of_limit"
|
|||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Airly air_quality entity based on a config entry."""
|
"""Set up Airly air_quality entity based on a config entry."""
|
||||||
name = config_entry.data[CONF_NAME]
|
name = config_entry.data[CONF_NAME]
|
||||||
|
latitude = config_entry.data[CONF_LATITUDE]
|
||||||
|
longitude = config_entry.data[CONF_LONGITUDE]
|
||||||
|
unique_id = f"{latitude}-{longitude}"
|
||||||
|
|
||||||
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||||
|
|
||||||
async_add_entities([AirlyAirQuality(data, name)], True)
|
async_add_entities([AirlyAirQuality(data, name, unique_id)], True)
|
||||||
|
|
||||||
|
|
||||||
def round_state(func):
|
def round_state(func):
|
||||||
@ -56,11 +59,12 @@ def round_state(func):
|
|||||||
class AirlyAirQuality(AirQualityEntity):
|
class AirlyAirQuality(AirQualityEntity):
|
||||||
"""Define an Airly air quality."""
|
"""Define an Airly air quality."""
|
||||||
|
|
||||||
def __init__(self, airly, name):
|
def __init__(self, airly, name, unique_id):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.airly = airly
|
self.airly = airly
|
||||||
self.data = airly.data
|
self.data = airly.data
|
||||||
self._name = name
|
self._name = name
|
||||||
|
self._unique_id = unique_id
|
||||||
self._pm_2_5 = None
|
self._pm_2_5 = None
|
||||||
self._pm_10 = None
|
self._pm_10 = None
|
||||||
self._aqi = None
|
self._aqi = None
|
||||||
@ -108,12 +112,12 @@ class AirlyAirQuality(AirQualityEntity):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique_id for this entity."""
|
"""Return a unique_id for this entity."""
|
||||||
return f"{self.airly.latitude}-{self.airly.longitude}"
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return bool(self.airly.data)
|
return bool(self.data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
@ -132,7 +136,6 @@ class AirlyAirQuality(AirQualityEntity):
|
|||||||
|
|
||||||
if self.airly.data:
|
if self.airly.data:
|
||||||
self.data = self.airly.data
|
self.data = self.airly.data
|
||||||
|
self._pm_10 = self.data[ATTR_API_PM10]
|
||||||
self._pm_10 = self.data[ATTR_API_PM10]
|
self._pm_2_5 = self.data[ATTR_API_PM25]
|
||||||
self._pm_2_5 = self.data[ATTR_API_PM25]
|
self._aqi = self.data[ATTR_API_CAQI]
|
||||||
self._aqi = self.data[ATTR_API_CAQI]
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"""Adds config flow for Airly."""
|
"""Adds config flow for Airly."""
|
||||||
import async_timeout
|
|
||||||
import voluptuous as vol
|
|
||||||
from airly import Airly
|
from airly import Airly
|
||||||
from airly.exceptions import AirlyError
|
from airly.exceptions import AirlyError
|
||||||
|
import async_timeout
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
|
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
@ -60,12 +62,16 @@ SENSOR_TYPES = {
|
|||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Airly sensor entities based on a config entry."""
|
"""Set up Airly sensor entities based on a config entry."""
|
||||||
name = config_entry.data[CONF_NAME]
|
name = config_entry.data[CONF_NAME]
|
||||||
|
latitude = config_entry.data[CONF_LATITUDE]
|
||||||
|
longitude = config_entry.data[CONF_LONGITUDE]
|
||||||
|
|
||||||
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
for sensor in SENSOR_TYPES:
|
for sensor in SENSOR_TYPES:
|
||||||
sensors.append(AirlySensor(data, name, sensor))
|
unique_id = f"{latitude}-{longitude}-{sensor.lower()}"
|
||||||
|
sensors.append(AirlySensor(data, name, sensor, unique_id))
|
||||||
|
|
||||||
async_add_entities(sensors, True)
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
@ -84,11 +90,12 @@ def round_state(func):
|
|||||||
class AirlySensor(Entity):
|
class AirlySensor(Entity):
|
||||||
"""Define an Airly sensor."""
|
"""Define an Airly sensor."""
|
||||||
|
|
||||||
def __init__(self, airly, name, kind):
|
def __init__(self, airly, name, kind, unique_id):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.airly = airly
|
self.airly = airly
|
||||||
self.data = airly.data
|
self.data = airly.data
|
||||||
self._name = name
|
self._name = name
|
||||||
|
self._unique_id = unique_id
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self._device_class = None
|
self._device_class = None
|
||||||
self._state = None
|
self._state = None
|
||||||
@ -130,7 +137,7 @@ class AirlySensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique_id for this entity."""
|
"""Return a unique_id for this entity."""
|
||||||
return f"{self.airly.latitude}-{self.airly.longitude}-{self.kind.lower()}"
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
@ -140,7 +147,7 @@ class AirlySensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return bool(self.airly.data)
|
return bool(self.data)
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update the sensor."""
|
"""Update the sensor."""
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "airvisual",
|
"domain": "airvisual",
|
||||||
"name": "Airvisual",
|
"name": "AirVisual",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||||
"requirements": [
|
"requirements": ["pyairvisual==3.0.1"],
|
||||||
"pyairvisual==3.0.1"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": [
|
"codeowners": ["@bachya"]
|
||||||
"@bachya"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ class AirVisualSensor(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||||
return f"{self._location_id}_{self._locale}_{self._type}"
|
return f"{self._location_id}_{self._locale}_{self._type}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "aladdin_connect",
|
"domain": "aladdin_connect",
|
||||||
"name": "Aladdin connect",
|
"name": "Aladdin Connect",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||||
"requirements": [
|
"requirements": ["aladdin_connect==0.3"],
|
||||||
"aladdin_connect==0.3"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
{
|
{
|
||||||
"device_automation": {
|
"device_automation": {
|
||||||
"action_type": {
|
"action_type": {
|
||||||
|
"arm_away": "Tilkobl {entity_name} ude",
|
||||||
|
"arm_home": "Tilkobl {entity_name} hjemme",
|
||||||
|
"arm_night": "Tilkobl {entity_name} nat",
|
||||||
|
"disarm": "Frakobl {entity_name}",
|
||||||
"trigger": "Udl\u00f8s {entity_name}"
|
"trigger": "Udl\u00f8s {entity_name}"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} tilkoblet ude",
|
||||||
|
"armed_home": "{entity_name} tilkoblet hjemme",
|
||||||
|
"armed_night": "{entity_name} tilkoblet nat",
|
||||||
|
"disarmed": "{entity_name} frakoblet",
|
||||||
|
"triggered": "{entity_name} udl\u00f8st"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"device_automation": {
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} Unterwegs",
|
||||||
|
"armed_home": "{entity_name} Zuhause",
|
||||||
|
"armed_night": "{entity_name} Nacht-Modus",
|
||||||
|
"disarmed": "{entity_name} deaktiviert",
|
||||||
|
"triggered": "{entity_name} ausgel\u00f6st"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,13 @@
|
|||||||
"arm_night": "Armar {entity_name} por la noche",
|
"arm_night": "Armar {entity_name} por la noche",
|
||||||
"disarm": "Desarmar {entity_name}",
|
"disarm": "Desarmar {entity_name}",
|
||||||
"trigger": "Lanzar {entity_name}"
|
"trigger": "Lanzar {entity_name}"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} armado fuera",
|
||||||
|
"armed_home": "{entity_name} armado en casa",
|
||||||
|
"armed_night": "{entity_name} armado modo noche",
|
||||||
|
"disarmed": "{entity_name} desarmado",
|
||||||
|
"triggered": "{entity_name} activado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,18 @@
|
|||||||
{
|
{
|
||||||
"device_automation": {
|
"device_automation": {
|
||||||
"action_type": {
|
"action_type": {
|
||||||
"arm_away": "Armer {entity_name} mode sortie",
|
"arm_away": "Armer {entity_name} en mode \"sortie\"",
|
||||||
"arm_home": "Armer {entity_name} mode \u00e0 la maison",
|
"arm_home": "Armer {entity_name} en mode \"maison\"",
|
||||||
"arm_night": "Armer {entity_name} mode nuit",
|
"arm_night": "Armer {entity_name} en mode \"nuit\"",
|
||||||
"disarm": "D\u00e9sarmer {entity_name}",
|
"disarm": "D\u00e9sarmer {entity_name}",
|
||||||
"trigger": "D\u00e9clencheur {entity_name}"
|
"trigger": "D\u00e9clencheur {entity_name}"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "Armer {entity_name} en mode \"sortie\"",
|
||||||
|
"armed_home": "Armer {entity_name} en mode \"maison\"",
|
||||||
|
"armed_night": "Armer {entity_name} en mode \"nuit\"",
|
||||||
|
"disarmed": "{entity_name} d\u00e9sarm\u00e9",
|
||||||
|
"triggered": "{entity_name} d\u00e9clench\u00e9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"device_automation": {
|
||||||
|
"action_type": {
|
||||||
|
"arm_away": "{entity_name} \u00e9les\u00edt\u00e9se t\u00e1voz\u00f3 m\u00f3dban",
|
||||||
|
"arm_home": "{entity_name} \u00e9les\u00edt\u00e9se otthon marad\u00f3 m\u00f3dban",
|
||||||
|
"arm_night": "{entity_name} \u00e9les\u00edt\u00e9se \u00e9jszakai m\u00f3dban",
|
||||||
|
"disarm": "{entity_name} hat\u00e1stalan\u00edt\u00e1sa",
|
||||||
|
"trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve",
|
||||||
|
"armed_home": "{entity_name} otthon marad\u00f3 m\u00f3dban lett \u00e9les\u00edtve",
|
||||||
|
"armed_night": "{entity_name} \u00e9jszakai m\u00f3dban lett \u00e9les\u00edtve",
|
||||||
|
"disarmed": "{entity_name} hat\u00e1stalan\u00edtva lett",
|
||||||
|
"triggered": "{entity_name} riaszt\u00e1sba ker\u00fclt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,13 @@
|
|||||||
"arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44",
|
"arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44",
|
||||||
"disarm": "{entity_name} \uacbd\ube44\ud574\uc81c",
|
"disarm": "{entity_name} \uacbd\ube44\ud574\uc81c",
|
||||||
"trigger": "{entity_name} \ud2b8\ub9ac\uac70"
|
"trigger": "{entity_name} \ud2b8\ub9ac\uac70"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c",
|
||||||
|
"armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c",
|
||||||
|
"armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c",
|
||||||
|
"disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c\ub420 \ub54c",
|
||||||
|
"triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub420 \ub54c"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,13 @@
|
|||||||
"arm_night": "Inschakelen {entity_name} nacht",
|
"arm_night": "Inschakelen {entity_name} nacht",
|
||||||
"disarm": "Uitschakelen {entity_name}",
|
"disarm": "Uitschakelen {entity_name}",
|
||||||
"trigger": "Trigger {entity_name}"
|
"trigger": "Trigger {entity_name}"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} afwezig ingeschakeld",
|
||||||
|
"armed_home": "{entity_name} thuis ingeschakeld",
|
||||||
|
"armed_night": "{entity_name} nachtstand ingeschakeld",
|
||||||
|
"disarmed": "{entity_name} uitgeschakeld",
|
||||||
|
"triggered": "{entity_name} geactiveerd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,9 +8,9 @@
|
|||||||
"trigger": "Utl\u00f8ser {entity_name}"
|
"trigger": "Utl\u00f8ser {entity_name}"
|
||||||
},
|
},
|
||||||
"trigger_type": {
|
"trigger_type": {
|
||||||
"armed_away": "{entity_name} borte sikkring ",
|
"armed_away": "{entity_name} aktivert borte",
|
||||||
"armed_home": "{entity_name} hjemme sikkring",
|
"armed_home": "{entity_name} aktivert hjemme",
|
||||||
"armed_night": "{entity_name} natt sikkring",
|
"armed_night": "{entity_name} aktivert natt",
|
||||||
"disarmed": "{entity_name} deaktivert",
|
"disarmed": "{entity_name} deaktivert",
|
||||||
"triggered": "{entity_name} utl\u00f8st"
|
"triggered": "{entity_name} utl\u00f8st"
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,13 @@
|
|||||||
"arm_night": "Armar {entity_name} noite",
|
"arm_night": "Armar {entity_name} noite",
|
||||||
"disarm": "Desarmar {entity_name}",
|
"disarm": "Desarmar {entity_name}",
|
||||||
"trigger": "Disparar {entidade_nome}"
|
"trigger": "Disparar {entidade_nome}"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"armed_away": "{entity_name} armado modo longe",
|
||||||
|
"armed_home": "{entity_name} armadado modo casa",
|
||||||
|
"armed_night": "{entity_name} armadado para noite",
|
||||||
|
"disarmed": "{entity_name} desarmado",
|
||||||
|
"triggered": "{entity_name} acionado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,9 +17,9 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||||
make_entity_service_schema,
|
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
PLATFORM_SCHEMA_BASE,
|
PLATFORM_SCHEMA_BASE,
|
||||||
|
make_entity_service_schema,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"domain": "alarm_control_panel",
|
"domain": "alarm_control_panel",
|
||||||
"name": "Alarm control panel",
|
"name": "Alarm Control Panel",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/alarm_control_panel",
|
"documentation": "https://www.home-assistant.io/integrations/alarm_control_panel",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": [],
|
||||||
|
"quality_scale": "internal"
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ CONF_DEVICE_BAUD = "baudrate"
|
|||||||
CONF_DEVICE_PATH = "path"
|
CONF_DEVICE_PATH = "path"
|
||||||
CONF_DEVICE_PORT = "port"
|
CONF_DEVICE_PORT = "port"
|
||||||
CONF_DEVICE_TYPE = "type"
|
CONF_DEVICE_TYPE = "type"
|
||||||
|
CONF_AUTO_BYPASS = "autobypass"
|
||||||
CONF_PANEL_DISPLAY = "panel_display"
|
CONF_PANEL_DISPLAY = "panel_display"
|
||||||
CONF_ZONE_NAME = "name"
|
CONF_ZONE_NAME = "name"
|
||||||
CONF_ZONE_TYPE = "type"
|
CONF_ZONE_TYPE = "type"
|
||||||
@ -39,6 +40,7 @@ DEFAULT_DEVICE_PORT = 10000
|
|||||||
DEFAULT_DEVICE_PATH = "/dev/ttyUSB0"
|
DEFAULT_DEVICE_PATH = "/dev/ttyUSB0"
|
||||||
DEFAULT_DEVICE_BAUD = 115200
|
DEFAULT_DEVICE_BAUD = 115200
|
||||||
|
|
||||||
|
DEFAULT_AUTO_BYPASS = False
|
||||||
DEFAULT_PANEL_DISPLAY = False
|
DEFAULT_PANEL_DISPLAY = False
|
||||||
|
|
||||||
DEFAULT_ZONE_TYPE = "opening"
|
DEFAULT_ZONE_TYPE = "opening"
|
||||||
@ -102,6 +104,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_PANEL_DISPLAY, default=DEFAULT_PANEL_DISPLAY
|
CONF_PANEL_DISPLAY, default=DEFAULT_PANEL_DISPLAY
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
|
vol.Optional(CONF_AUTO_BYPASS, default=DEFAULT_AUTO_BYPASS): cv.boolean,
|
||||||
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
|
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -4,8 +4,8 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.alarm_control_panel import (
|
from homeassistant.components.alarm_control_panel import (
|
||||||
AlarmControlPanel,
|
|
||||||
FORMAT_NUMBER,
|
FORMAT_NUMBER,
|
||||||
|
AlarmControlPanel,
|
||||||
)
|
)
|
||||||
from homeassistant.components.alarm_control_panel.const import (
|
from homeassistant.components.alarm_control_panel.const import (
|
||||||
SUPPORT_ALARM_ARM_AWAY,
|
SUPPORT_ALARM_ARM_AWAY,
|
||||||
@ -35,7 +35,7 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({vol.Required(ATTR_KEYPRESS): cv.string})
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up for AlarmDecoder alarm panels."""
|
"""Set up for AlarmDecoder alarm panels."""
|
||||||
device = AlarmDecoderAlarmPanel()
|
device = AlarmDecoderAlarmPanel(discovery_info["autobypass"])
|
||||||
add_entities([device])
|
add_entities([device])
|
||||||
|
|
||||||
def alarm_toggle_chime_handler(service):
|
def alarm_toggle_chime_handler(service):
|
||||||
@ -66,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
||||||
"""Representation of an AlarmDecoder-based alarm panel."""
|
"""Representation of an AlarmDecoder-based alarm panel."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, auto_bypass):
|
||||||
"""Initialize the alarm panel."""
|
"""Initialize the alarm panel."""
|
||||||
self._display = ""
|
self._display = ""
|
||||||
self._name = "Alarm Panel"
|
self._name = "Alarm Panel"
|
||||||
@ -80,6 +80,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
|||||||
self._programming_mode = None
|
self._programming_mode = None
|
||||||
self._ready = None
|
self._ready = None
|
||||||
self._zone_bypassed = None
|
self._zone_bypassed = None
|
||||||
|
self._auto_bypass = auto_bypass
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
@ -158,11 +159,15 @@ class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
|||||||
def alarm_arm_away(self, code=None):
|
def alarm_arm_away(self, code=None):
|
||||||
"""Send arm away command."""
|
"""Send arm away command."""
|
||||||
if code:
|
if code:
|
||||||
|
if self._auto_bypass:
|
||||||
|
self.hass.data[DATA_AD].send(f"{code!s}6#")
|
||||||
self.hass.data[DATA_AD].send(f"{code!s}2")
|
self.hass.data[DATA_AD].send(f"{code!s}2")
|
||||||
|
|
||||||
def alarm_arm_home(self, code=None):
|
def alarm_arm_home(self, code=None):
|
||||||
"""Send arm home command."""
|
"""Send arm home command."""
|
||||||
if code:
|
if code:
|
||||||
|
if self._auto_bypass:
|
||||||
|
self.hass.data[DATA_AD].send(f"{code!s}6#")
|
||||||
self.hass.data[DATA_AD].send(f"{code!s}3")
|
self.hass.data[DATA_AD].send(f"{code!s}3")
|
||||||
|
|
||||||
def alarm_arm_night(self, code=None):
|
def alarm_arm_night(self, code=None):
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "alarmdecoder",
|
"domain": "alarmdecoder",
|
||||||
"name": "Alarmdecoder",
|
"name": "AlarmDecoder",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
|
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
|
||||||
"requirements": [
|
"requirements": ["alarmdecoder==1.13.9"],
|
||||||
"alarmdecoder==1.13.2"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "alarmdotcom",
|
"domain": "alarmdotcom",
|
||||||
"name": "Alarmdotcom",
|
"name": "Alarm.com",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/alarmdotcom",
|
"documentation": "https://www.home-assistant.io/integrations/alarmdotcom",
|
||||||
"requirements": [
|
"requirements": ["pyalarmdotcom==0.3.2"],
|
||||||
"pyalarmdotcom==0.3.2"
|
|
||||||
],
|
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
"""Support for repeating alerts when conditions are met."""
|
"""Support for repeating alerts when conditions are met."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
|
ATTR_DATA,
|
||||||
ATTR_MESSAGE,
|
ATTR_MESSAGE,
|
||||||
ATTR_TITLE,
|
ATTR_TITLE,
|
||||||
ATTR_DATA,
|
|
||||||
DOMAIN as DOMAIN_NOTIFY,
|
DOMAIN as DOMAIN_NOTIFY,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
CONF_ENTITY_ID,
|
CONF_ENTITY_ID,
|
||||||
STATE_IDLE,
|
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
STATE_ON,
|
|
||||||
STATE_OFF,
|
|
||||||
SERVICE_TURN_ON,
|
|
||||||
SERVICE_TURN_OFF,
|
|
||||||
SERVICE_TOGGLE,
|
SERVICE_TOGGLE,
|
||||||
ATTR_ENTITY_ID,
|
SERVICE_TURN_OFF,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
STATE_IDLE,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import service, event
|
from homeassistant.helpers import event, service
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.util.dt import now
|
from homeassistant.util.dt import now
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ class Alert(ToggleEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""HASS need not poll these entities."""
|
"""Home Assistant need not poll these entities."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/alert",
|
"documentation": "https://www.home-assistant.io/integrations/alert",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"after_dependencies": [
|
"after_dependencies": ["notify"],
|
||||||
"notify"
|
"codeowners": [],
|
||||||
],
|
"quality_scale": "internal"
|
||||||
"codeowners": []
|
|
||||||
}
|
}
|
||||||
|
@ -3,31 +3,33 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
|
||||||
from homeassistant.helpers import entityfilter
|
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.helpers import config_validation as cv, entityfilter
|
||||||
|
|
||||||
from . import flash_briefings, intent, smart_home_http
|
from . import flash_briefings, intent, smart_home_http
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_AUDIO,
|
CONF_AUDIO,
|
||||||
CONF_CLIENT_ID,
|
CONF_CLIENT_ID,
|
||||||
CONF_CLIENT_SECRET,
|
CONF_CLIENT_SECRET,
|
||||||
|
CONF_DESCRIPTION,
|
||||||
|
CONF_DISPLAY_CATEGORIES,
|
||||||
CONF_DISPLAY_URL,
|
CONF_DISPLAY_URL,
|
||||||
CONF_ENDPOINT,
|
CONF_ENDPOINT,
|
||||||
|
CONF_ENTITY_CONFIG,
|
||||||
|
CONF_FILTER,
|
||||||
|
CONF_LOCALE,
|
||||||
|
CONF_SUPPORTED_LOCALES,
|
||||||
CONF_TEXT,
|
CONF_TEXT,
|
||||||
CONF_TITLE,
|
CONF_TITLE,
|
||||||
CONF_UID,
|
CONF_UID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
CONF_FILTER,
|
|
||||||
CONF_ENTITY_CONFIG,
|
|
||||||
CONF_DESCRIPTION,
|
|
||||||
CONF_DISPLAY_CATEGORIES,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_FLASH_BRIEFINGS = "flash_briefings"
|
CONF_FLASH_BRIEFINGS = "flash_briefings"
|
||||||
CONF_SMART_HOME = "smart_home"
|
CONF_SMART_HOME = "smart_home"
|
||||||
|
DEFAULT_LOCALE = "en-US"
|
||||||
|
|
||||||
ALEXA_ENTITY_SCHEMA = vol.Schema(
|
ALEXA_ENTITY_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -42,6 +44,9 @@ SMART_HOME_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_ENDPOINT): cv.string,
|
vol.Optional(CONF_ENDPOINT): cv.string,
|
||||||
vol.Optional(CONF_CLIENT_ID): cv.string,
|
vol.Optional(CONF_CLIENT_ID): cv.string,
|
||||||
vol.Optional(CONF_CLIENT_SECRET): cv.string,
|
vol.Optional(CONF_CLIENT_SECRET): cv.string,
|
||||||
|
vol.Optional(CONF_LOCALE, default=DEFAULT_LOCALE): vol.In(
|
||||||
|
CONF_SUPPORTED_LOCALES
|
||||||
|
),
|
||||||
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
|
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
|
||||||
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA},
|
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA},
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Support for Alexa skill auth."""
|
"""Support for Alexa skill auth."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ class Auth:
|
|||||||
"client_secret": self.client_secret,
|
"client_secret": self.client_secret,
|
||||||
}
|
}
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Calling LWA to get the access token (first time), " "with: %s",
|
"Calling LWA to get the access token (first time), with: %s",
|
||||||
json.dumps(lwa_params),
|
json.dumps(lwa_params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,12 @@
|
|||||||
"""Config helpers for Alexa."""
|
"""Config helpers for Alexa."""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
from .state_report import async_enable_proactive_mode
|
from .state_report import async_enable_proactive_mode
|
||||||
|
|
||||||
|
|
||||||
class AbstractConfig:
|
class AbstractConfig(ABC):
|
||||||
"""Hold the configuration for Alexa."""
|
"""Hold the configuration for Alexa."""
|
||||||
|
|
||||||
_unsub_proactive_report = None
|
_unsub_proactive_report = None
|
||||||
@ -28,6 +30,11 @@ class AbstractConfig:
|
|||||||
"""Endpoint for report state."""
|
"""Endpoint for report state."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def locale(self):
|
||||||
|
"""Return config locale."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_config(self):
|
def entity_config(self):
|
||||||
"""Return entity config."""
|
"""Return entity config."""
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""Constants for the Alexa integration."""
|
"""Constants for the Alexa integration."""
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
|
||||||
from homeassistant.components.climate import const as climate
|
|
||||||
from homeassistant.components import fan
|
from homeassistant.components import fan
|
||||||
|
from homeassistant.components.climate import const as climate
|
||||||
|
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
DOMAIN = "alexa"
|
DOMAIN = "alexa"
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ CONF_ENTITY_CONFIG = "entity_config"
|
|||||||
CONF_ENDPOINT = "endpoint"
|
CONF_ENDPOINT = "endpoint"
|
||||||
CONF_CLIENT_ID = "client_id"
|
CONF_CLIENT_ID = "client_id"
|
||||||
CONF_CLIENT_SECRET = "client_secret"
|
CONF_CLIENT_SECRET = "client_secret"
|
||||||
|
CONF_LOCALE = "locale"
|
||||||
|
|
||||||
ATTR_UID = "uid"
|
ATTR_UID = "uid"
|
||||||
ATTR_UPDATE_DATE = "updateDate"
|
ATTR_UPDATE_DATE = "updateDate"
|
||||||
@ -42,6 +43,20 @@ API_CHANGE = "change"
|
|||||||
|
|
||||||
CONF_DESCRIPTION = "description"
|
CONF_DESCRIPTION = "description"
|
||||||
CONF_DISPLAY_CATEGORIES = "display_categories"
|
CONF_DISPLAY_CATEGORIES = "display_categories"
|
||||||
|
CONF_SUPPORTED_LOCALES = (
|
||||||
|
"de-DE",
|
||||||
|
"en-AU",
|
||||||
|
"en-CA",
|
||||||
|
"en-GB",
|
||||||
|
"en-IN",
|
||||||
|
"en-US",
|
||||||
|
"es-ES",
|
||||||
|
"es-MX",
|
||||||
|
"fr-CA",
|
||||||
|
"fr-FR",
|
||||||
|
"it-IT",
|
||||||
|
"ja-JP",
|
||||||
|
)
|
||||||
|
|
||||||
API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"}
|
API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"}
|
||||||
|
|
||||||
@ -117,163 +132,6 @@ class Cause:
|
|||||||
VOICE_INTERACTION = "VOICE_INTERACTION"
|
VOICE_INTERACTION = "VOICE_INTERACTION"
|
||||||
|
|
||||||
|
|
||||||
class Catalog:
|
|
||||||
"""The Global Alexa catalog.
|
|
||||||
|
|
||||||
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog
|
|
||||||
|
|
||||||
You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units.
|
|
||||||
This catalog is localized into all the languages that Alexa supports.
|
|
||||||
|
|
||||||
You can reference the following catalog of pre-defined friendly names.
|
|
||||||
Each item in the following list is an asset identifier followed by its supported friendly names.
|
|
||||||
The first friendly name for each identifier is the one displayed in the Alexa mobile app.
|
|
||||||
"""
|
|
||||||
|
|
||||||
LABEL_ASSET = "asset"
|
|
||||||
LABEL_TEXT = "text"
|
|
||||||
|
|
||||||
# Shower
|
|
||||||
DEVICENAME_SHOWER = "Alexa.DeviceName.Shower"
|
|
||||||
|
|
||||||
# Washer, Washing Machine
|
|
||||||
DEVICENAME_WASHER = "Alexa.DeviceName.Washer"
|
|
||||||
|
|
||||||
# Router, Internet Router, Network Router, Wifi Router, Net Router
|
|
||||||
DEVICENAME_ROUTER = "Alexa.DeviceName.Router"
|
|
||||||
|
|
||||||
# Fan, Blower
|
|
||||||
DEVICENAME_FAN = "Alexa.DeviceName.Fan"
|
|
||||||
|
|
||||||
# Air Purifier, Air Cleaner,Clean Air Machine
|
|
||||||
DEVICENAME_AIRPURIFIER = "Alexa.DeviceName.AirPurifier"
|
|
||||||
|
|
||||||
# Space Heater, Portable Heater
|
|
||||||
DEVICENAME_SPACEHEATER = "Alexa.DeviceName.SpaceHeater"
|
|
||||||
|
|
||||||
# Rain Head, Overhead shower, Rain Shower, Rain Spout, Rain Faucet
|
|
||||||
SHOWER_RAINHEAD = "Alexa.Shower.RainHead"
|
|
||||||
|
|
||||||
# Handheld Shower, Shower Wand, Hand Shower
|
|
||||||
SHOWER_HANDHELD = "Alexa.Shower.HandHeld"
|
|
||||||
|
|
||||||
# Water Temperature, Water Temp, Water Heat
|
|
||||||
SETTING_WATERTEMPERATURE = "Alexa.Setting.WaterTemperature"
|
|
||||||
|
|
||||||
# Temperature, Temp
|
|
||||||
SETTING_TEMPERATURE = "Alexa.Setting.Temperature"
|
|
||||||
|
|
||||||
# Wash Cycle, Wash Preset, Wash setting
|
|
||||||
SETTING_WASHCYCLE = "Alexa.Setting.WashCycle"
|
|
||||||
|
|
||||||
# 2.4G Guest Wi-Fi, 2.4G Guest Network, Guest Network 2.4G, 2G Guest Wifi
|
|
||||||
SETTING_2GGUESTWIFI = "Alexa.Setting.2GGuestWiFi"
|
|
||||||
|
|
||||||
# 5G Guest Wi-Fi, 5G Guest Network, Guest Network 5G, 5G Guest Wifi
|
|
||||||
SETTING_5GGUESTWIFI = "Alexa.Setting.5GGuestWiFi"
|
|
||||||
|
|
||||||
# Guest Wi-fi, Guest Network, Guest Net
|
|
||||||
SETTING_GUESTWIFI = "Alexa.Setting.GuestWiFi"
|
|
||||||
|
|
||||||
# Auto, Automatic, Automatic Mode, Auto Mode
|
|
||||||
SETTING_AUTO = "Alexa.Setting.Auto"
|
|
||||||
|
|
||||||
# #Night, Night Mode
|
|
||||||
SETTING_NIGHT = "Alexa.Setting.Night"
|
|
||||||
|
|
||||||
# Quiet, Quiet Mode, Noiseless, Silent
|
|
||||||
SETTING_QUIET = "Alexa.Setting.Quiet"
|
|
||||||
|
|
||||||
# Oscillate, Swivel, Oscillation, Spin, Back and forth
|
|
||||||
SETTING_OSCILLATE = "Alexa.Setting.Oscillate"
|
|
||||||
|
|
||||||
# Fan Speed, Airflow speed, Wind Speed, Air speed, Air velocity
|
|
||||||
SETTING_FANSPEED = "Alexa.Setting.FanSpeed"
|
|
||||||
|
|
||||||
# Preset, Setting
|
|
||||||
SETTING_PRESET = "Alexa.Setting.Preset"
|
|
||||||
|
|
||||||
# Mode
|
|
||||||
SETTING_MODE = "Alexa.Setting.Mode"
|
|
||||||
|
|
||||||
# Direction
|
|
||||||
SETTING_DIRECTION = "Alexa.Setting.Direction"
|
|
||||||
|
|
||||||
# Delicates, Delicate
|
|
||||||
VALUE_DELICATE = "Alexa.Value.Delicate"
|
|
||||||
|
|
||||||
# Quick Wash, Fast Wash, Wash Quickly, Speed Wash
|
|
||||||
VALUE_QUICKWASH = "Alexa.Value.QuickWash"
|
|
||||||
|
|
||||||
# Maximum, Max
|
|
||||||
VALUE_MAXIMUM = "Alexa.Value.Maximum"
|
|
||||||
|
|
||||||
# Minimum, Min
|
|
||||||
VALUE_MINIMUM = "Alexa.Value.Minimum"
|
|
||||||
|
|
||||||
# High
|
|
||||||
VALUE_HIGH = "Alexa.Value.High"
|
|
||||||
|
|
||||||
# Low
|
|
||||||
VALUE_LOW = "Alexa.Value.Low"
|
|
||||||
|
|
||||||
# Medium, Mid
|
|
||||||
VALUE_MEDIUM = "Alexa.Value.Medium"
|
|
||||||
|
|
||||||
|
|
||||||
class Unit:
|
|
||||||
"""Alexa Units of Measure.
|
|
||||||
|
|
||||||
https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#units-of-measure
|
|
||||||
"""
|
|
||||||
|
|
||||||
ANGLE_DEGREES = "Alexa.Unit.Angle.Degrees"
|
|
||||||
|
|
||||||
ANGLE_RADIANS = "Alexa.Unit.Angle.Radians"
|
|
||||||
|
|
||||||
DISTANCE_FEET = "Alexa.Unit.Distance.Feet"
|
|
||||||
|
|
||||||
DISTANCE_INCHES = "Alexa.Unit.Distance.Inches"
|
|
||||||
|
|
||||||
DISTANCE_KILOMETERS = "Alexa.Unit.Distance.Kilometers"
|
|
||||||
|
|
||||||
DISTANCE_METERS = "Alexa.Unit.Distance.Meters"
|
|
||||||
|
|
||||||
DISTANCE_MILES = "Alexa.Unit.Distance.Miles"
|
|
||||||
|
|
||||||
DISTANCE_YARDS = "Alexa.Unit.Distance.Yards"
|
|
||||||
|
|
||||||
MASS_GRAMS = "Alexa.Unit.Mass.Grams"
|
|
||||||
|
|
||||||
MASS_KILOGRAMS = "Alexa.Unit.Mass.Kilograms"
|
|
||||||
|
|
||||||
PERCENT = "Alexa.Unit.Percent"
|
|
||||||
|
|
||||||
TEMPERATURE_CELSIUS = "Alexa.Unit.Temperature.Celsius"
|
|
||||||
|
|
||||||
TEMPERATURE_DEGREES = "Alexa.Unit.Temperature.Degrees"
|
|
||||||
|
|
||||||
TEMPERATURE_FAHRENHEIT = "Alexa.Unit.Temperature.Fahrenheit"
|
|
||||||
|
|
||||||
TEMPERATURE_KELVIN = "Alexa.Unit.Temperature.Kelvin"
|
|
||||||
|
|
||||||
VOLUME_CUBICFEET = "Alexa.Unit.Volume.CubicFeet"
|
|
||||||
|
|
||||||
VOLUME_CUBICMETERS = "Alexa.Unit.Volume.CubicMeters"
|
|
||||||
|
|
||||||
VOLUME_GALLONS = "Alexa.Unit.Volume.Gallons"
|
|
||||||
|
|
||||||
VOLUME_LITERS = "Alexa.Unit.Volume.Liters"
|
|
||||||
|
|
||||||
VOLUME_PINTS = "Alexa.Unit.Volume.Pints"
|
|
||||||
|
|
||||||
VOLUME_QUARTS = "Alexa.Unit.Volume.Quarts"
|
|
||||||
|
|
||||||
WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces"
|
|
||||||
|
|
||||||
WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds"
|
|
||||||
|
|
||||||
|
|
||||||
class Inputs:
|
class Inputs:
|
||||||
"""Valid names for the InputController.
|
"""Valid names for the InputController.
|
||||||
|
|
||||||
@ -353,3 +211,11 @@ class Inputs:
|
|||||||
"video3": "VIDEO 3",
|
"video3": "VIDEO 3",
|
||||||
"xbox": "XBOX",
|
"xbox": "XBOX",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VALID_SOUND_MODE_MAP = {
|
||||||
|
"movie": "MOVIE",
|
||||||
|
"music": "MUSIC",
|
||||||
|
"night": "NIGHT",
|
||||||
|
"sport": "SPORT",
|
||||||
|
"tv": "TV",
|
||||||
|
}
|
||||||
|
@ -1,7 +1,26 @@
|
|||||||
"""Alexa entity adapters."""
|
"""Alexa entity adapters."""
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.components import (
|
||||||
|
alarm_control_panel,
|
||||||
|
alert,
|
||||||
|
automation,
|
||||||
|
binary_sensor,
|
||||||
|
cover,
|
||||||
|
fan,
|
||||||
|
group,
|
||||||
|
image_processing,
|
||||||
|
input_boolean,
|
||||||
|
input_number,
|
||||||
|
light,
|
||||||
|
lock,
|
||||||
|
media_player,
|
||||||
|
scene,
|
||||||
|
script,
|
||||||
|
sensor,
|
||||||
|
switch,
|
||||||
|
)
|
||||||
|
from homeassistant.components.climate import const as climate
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
@ -11,27 +30,9 @@ from homeassistant.const import (
|
|||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
from homeassistant.components.climate import const as climate
|
|
||||||
from homeassistant.components import (
|
|
||||||
alarm_control_panel,
|
|
||||||
alert,
|
|
||||||
automation,
|
|
||||||
binary_sensor,
|
|
||||||
cover,
|
|
||||||
fan,
|
|
||||||
group,
|
|
||||||
input_boolean,
|
|
||||||
light,
|
|
||||||
lock,
|
|
||||||
media_player,
|
|
||||||
scene,
|
|
||||||
script,
|
|
||||||
sensor,
|
|
||||||
switch,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
|
|
||||||
from .capabilities import (
|
from .capabilities import (
|
||||||
Alexa,
|
Alexa,
|
||||||
AlexaBrightnessController,
|
AlexaBrightnessController,
|
||||||
@ -41,6 +42,8 @@ from .capabilities import (
|
|||||||
AlexaContactSensor,
|
AlexaContactSensor,
|
||||||
AlexaDoorbellEventSource,
|
AlexaDoorbellEventSource,
|
||||||
AlexaEndpointHealth,
|
AlexaEndpointHealth,
|
||||||
|
AlexaEqualizerController,
|
||||||
|
AlexaEventDetectionSensor,
|
||||||
AlexaInputController,
|
AlexaInputController,
|
||||||
AlexaLockController,
|
AlexaLockController,
|
||||||
AlexaModeController,
|
AlexaModeController,
|
||||||
@ -60,6 +63,7 @@ from .capabilities import (
|
|||||||
AlexaThermostatController,
|
AlexaThermostatController,
|
||||||
AlexaToggleController,
|
AlexaToggleController,
|
||||||
)
|
)
|
||||||
|
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
|
||||||
|
|
||||||
ENTITY_ADAPTERS = Registry()
|
ENTITY_ADAPTERS = Registry()
|
||||||
|
|
||||||
@ -81,6 +85,9 @@ class DisplayCategory:
|
|||||||
# Indicates media devices with video or photo capabilities.
|
# Indicates media devices with video or photo capabilities.
|
||||||
CAMERA = "CAMERA"
|
CAMERA = "CAMERA"
|
||||||
|
|
||||||
|
# Indicates a non-mobile computer, such as a desktop computer.
|
||||||
|
COMPUTER = "COMPUTER"
|
||||||
|
|
||||||
# Indicates an endpoint that detects and reports contact.
|
# Indicates an endpoint that detects and reports contact.
|
||||||
CONTACT_SENSOR = "CONTACT_SENSOR"
|
CONTACT_SENSOR = "CONTACT_SENSOR"
|
||||||
|
|
||||||
@ -90,27 +97,60 @@ class DisplayCategory:
|
|||||||
# Indicates a doorbell.
|
# Indicates a doorbell.
|
||||||
DOORBELL = "DOORBELL"
|
DOORBELL = "DOORBELL"
|
||||||
|
|
||||||
|
# Indicates a window covering on the outside of a structure.
|
||||||
|
EXTERIOR_BLIND = "EXTERIOR_BLIND"
|
||||||
|
|
||||||
# Indicates a fan.
|
# Indicates a fan.
|
||||||
FAN = "FAN"
|
FAN = "FAN"
|
||||||
|
|
||||||
|
# Indicates a game console, such as Microsoft Xbox or Nintendo Switch
|
||||||
|
GAME_CONSOLE = "GAME_CONSOLE"
|
||||||
|
|
||||||
|
# Indicates a garage door. Garage doors must implement the ModeController interface to open and close the door.
|
||||||
|
GARAGE_DOOR = "GARAGE_DOOR"
|
||||||
|
|
||||||
|
# Indicates a window covering on the inside of a structure.
|
||||||
|
INTERIOR_BLIND = "INTERIOR_BLIND"
|
||||||
|
|
||||||
|
# Indicates a laptop or other mobile computer.
|
||||||
|
LAPTOP = "LAPTOP"
|
||||||
|
|
||||||
# Indicates light sources or fixtures.
|
# Indicates light sources or fixtures.
|
||||||
LIGHT = "LIGHT"
|
LIGHT = "LIGHT"
|
||||||
|
|
||||||
# Indicates a microwave oven.
|
# Indicates a microwave oven.
|
||||||
MICROWAVE = "MICROWAVE"
|
MICROWAVE = "MICROWAVE"
|
||||||
|
|
||||||
|
# Indicates a mobile phone.
|
||||||
|
MOBILE_PHONE = "MOBILE_PHONE"
|
||||||
|
|
||||||
# Indicates an endpoint that detects and reports motion.
|
# Indicates an endpoint that detects and reports motion.
|
||||||
MOTION_SENSOR = "MOTION_SENSOR"
|
MOTION_SENSOR = "MOTION_SENSOR"
|
||||||
|
|
||||||
|
# Indicates a network-connected music system.
|
||||||
|
MUSIC_SYSTEM = "MUSIC_SYSTEM"
|
||||||
|
|
||||||
# An endpoint that cannot be described in on of the other categories.
|
# An endpoint that cannot be described in on of the other categories.
|
||||||
OTHER = "OTHER"
|
OTHER = "OTHER"
|
||||||
|
|
||||||
|
# Indicates a network router.
|
||||||
|
NETWORK_HARDWARE = "NETWORK_HARDWARE"
|
||||||
|
|
||||||
|
# Indicates an oven cooking appliance.
|
||||||
|
OVEN = "OVEN"
|
||||||
|
|
||||||
|
# Indicates a non-mobile phone, such as landline or an IP phone.
|
||||||
|
PHONE = "PHONE"
|
||||||
|
|
||||||
# Describes a combination of devices set to a specific state, when the
|
# Describes a combination of devices set to a specific state, when the
|
||||||
# order of the state change is not important. For example a bedtime scene
|
# order of the state change is not important. For example a bedtime scene
|
||||||
# might include turning off lights and lowering the thermostat, but the
|
# might include turning off lights and lowering the thermostat, but the
|
||||||
# order is unimportant. Applies to Scenes
|
# order is unimportant. Applies to Scenes
|
||||||
SCENE_TRIGGER = "SCENE_TRIGGER"
|
SCENE_TRIGGER = "SCENE_TRIGGER"
|
||||||
|
|
||||||
|
# Indicates a projector screen.
|
||||||
|
SCREEN = "SCREEN"
|
||||||
|
|
||||||
# Indicates a security panel.
|
# Indicates a security panel.
|
||||||
SECURITY_PANEL = "SECURITY_PANEL"
|
SECURITY_PANEL = "SECURITY_PANEL"
|
||||||
|
|
||||||
@ -124,10 +164,16 @@ class DisplayCategory:
|
|||||||
# Indicates the endpoint is a speaker or speaker system.
|
# Indicates the endpoint is a speaker or speaker system.
|
||||||
SPEAKER = "SPEAKER"
|
SPEAKER = "SPEAKER"
|
||||||
|
|
||||||
|
# Indicates a streaming device such as Apple TV, Chromecast, or Roku.
|
||||||
|
STREAMING_DEVICE = "STREAMING_DEVICE"
|
||||||
|
|
||||||
# Indicates in-wall switches wired to the electrical system. Can control a
|
# Indicates in-wall switches wired to the electrical system. Can control a
|
||||||
# variety of devices.
|
# variety of devices.
|
||||||
SWITCH = "SWITCH"
|
SWITCH = "SWITCH"
|
||||||
|
|
||||||
|
# Indicates a tablet computer.
|
||||||
|
TABLET = "TABLET"
|
||||||
|
|
||||||
# Indicates endpoints that report the temperature only.
|
# Indicates endpoints that report the temperature only.
|
||||||
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
|
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
|
||||||
|
|
||||||
@ -138,6 +184,9 @@ class DisplayCategory:
|
|||||||
# Indicates the endpoint is a television.
|
# Indicates the endpoint is a television.
|
||||||
TV = "TV"
|
TV = "TV"
|
||||||
|
|
||||||
|
# Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear.
|
||||||
|
WEARABLE = "WEARABLE"
|
||||||
|
|
||||||
|
|
||||||
class AlexaEntity:
|
class AlexaEntity:
|
||||||
"""An adaptation of an entity, expressed in Alexa's terms.
|
"""An adaptation of an entity, expressed in Alexa's terms.
|
||||||
@ -211,16 +260,24 @@ class AlexaEntity:
|
|||||||
|
|
||||||
def serialize_discovery(self):
|
def serialize_discovery(self):
|
||||||
"""Serialize the entity for discovery."""
|
"""Serialize the entity for discovery."""
|
||||||
return {
|
result = {
|
||||||
"displayCategories": self.display_categories(),
|
"displayCategories": self.display_categories(),
|
||||||
"cookie": {},
|
"cookie": {},
|
||||||
"endpointId": self.alexa_id(),
|
"endpointId": self.alexa_id(),
|
||||||
"friendlyName": self.friendly_name(),
|
"friendlyName": self.friendly_name(),
|
||||||
"description": self.description(),
|
"description": self.description(),
|
||||||
"manufacturerName": "Home Assistant",
|
"manufacturerName": "Home Assistant",
|
||||||
"capabilities": [i.serialize_discovery() for i in self.interfaces()],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locale = self.config.locale
|
||||||
|
capabilities = []
|
||||||
|
for i in self.interfaces():
|
||||||
|
if locale in i.supported_locales:
|
||||||
|
capabilities.append(i.serialize_discovery())
|
||||||
|
result["capabilities"] = capabilities
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_get_entities(hass, config) -> List[AlexaEntity]:
|
def async_get_entities(hass, config) -> List[AlexaEntity]:
|
||||||
@ -316,20 +373,40 @@ class CoverCapabilities(AlexaEntity):
|
|||||||
def default_display_categories(self):
|
def default_display_categories(self):
|
||||||
"""Return the display categories for this entity."""
|
"""Return the display categories for this entity."""
|
||||||
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
|
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
|
||||||
if device_class in (cover.DEVICE_CLASS_GARAGE, cover.DEVICE_CLASS_DOOR):
|
if device_class == cover.DEVICE_CLASS_GARAGE:
|
||||||
|
return [DisplayCategory.GARAGE_DOOR]
|
||||||
|
if device_class == cover.DEVICE_CLASS_DOOR:
|
||||||
return [DisplayCategory.DOOR]
|
return [DisplayCategory.DOOR]
|
||||||
|
if device_class in (
|
||||||
|
cover.DEVICE_CLASS_BLIND,
|
||||||
|
cover.DEVICE_CLASS_SHADE,
|
||||||
|
cover.DEVICE_CLASS_CURTAIN,
|
||||||
|
):
|
||||||
|
return [DisplayCategory.INTERIOR_BLIND]
|
||||||
|
if device_class in (
|
||||||
|
cover.DEVICE_CLASS_WINDOW,
|
||||||
|
cover.DEVICE_CLASS_AWNING,
|
||||||
|
cover.DEVICE_CLASS_SHUTTER,
|
||||||
|
):
|
||||||
|
return [DisplayCategory.EXTERIOR_BLIND]
|
||||||
|
|
||||||
return [DisplayCategory.OTHER]
|
return [DisplayCategory.OTHER]
|
||||||
|
|
||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
yield AlexaPowerController(self.entity)
|
|
||||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if supported & cover.SUPPORT_SET_POSITION:
|
if supported & cover.SUPPORT_SET_POSITION:
|
||||||
yield AlexaPercentageController(self.entity)
|
yield AlexaRangeController(
|
||||||
if supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN):
|
self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}"
|
||||||
|
)
|
||||||
|
elif supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN):
|
||||||
yield AlexaModeController(
|
yield AlexaModeController(
|
||||||
self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}"
|
self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}"
|
||||||
)
|
)
|
||||||
|
if supported & cover.SUPPORT_SET_TILT_POSITION:
|
||||||
|
yield AlexaRangeController(
|
||||||
|
self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}"
|
||||||
|
)
|
||||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.hass)
|
yield Alexa(self.hass)
|
||||||
|
|
||||||
@ -353,6 +430,7 @@ class LightCapabilities(AlexaEntity):
|
|||||||
yield AlexaColorController(self.entity)
|
yield AlexaColorController(self.entity)
|
||||||
if supported & light.SUPPORT_COLOR_TEMP:
|
if supported & light.SUPPORT_COLOR_TEMP:
|
||||||
yield AlexaColorTemperatureController(self.entity)
|
yield AlexaColorTemperatureController(self.entity)
|
||||||
|
|
||||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.hass)
|
yield Alexa(self.hass)
|
||||||
|
|
||||||
@ -368,6 +446,7 @@ class FanCapabilities(AlexaEntity):
|
|||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
yield AlexaPowerController(self.entity)
|
yield AlexaPowerController(self.entity)
|
||||||
|
|
||||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if supported & fan.SUPPORT_SET_SPEED:
|
if supported & fan.SUPPORT_SET_SPEED:
|
||||||
yield AlexaPercentageController(self.entity)
|
yield AlexaPercentageController(self.entity)
|
||||||
@ -375,7 +454,6 @@ class FanCapabilities(AlexaEntity):
|
|||||||
yield AlexaRangeController(
|
yield AlexaRangeController(
|
||||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
|
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if supported & fan.SUPPORT_OSCILLATE:
|
if supported & fan.SUPPORT_OSCILLATE:
|
||||||
yield AlexaToggleController(
|
yield AlexaToggleController(
|
||||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
|
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
|
||||||
@ -453,6 +531,9 @@ class MediaPlayerCapabilities(AlexaEntity):
|
|||||||
if supported & media_player.const.SUPPORT_PLAY_MEDIA:
|
if supported & media_player.const.SUPPORT_PLAY_MEDIA:
|
||||||
yield AlexaChannelController(self.entity)
|
yield AlexaChannelController(self.entity)
|
||||||
|
|
||||||
|
if supported & media_player.const.SUPPORT_SELECT_SOUND_MODE:
|
||||||
|
yield AlexaEqualizerController(self.entity)
|
||||||
|
|
||||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.hass)
|
yield Alexa(self.hass)
|
||||||
|
|
||||||
@ -522,6 +603,7 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||||||
|
|
||||||
TYPE_CONTACT = "contact"
|
TYPE_CONTACT = "contact"
|
||||||
TYPE_MOTION = "motion"
|
TYPE_MOTION = "motion"
|
||||||
|
TYPE_PRESENCE = "presence"
|
||||||
|
|
||||||
def default_display_categories(self):
|
def default_display_categories(self):
|
||||||
"""Return the display categories for this entity."""
|
"""Return the display categories for this entity."""
|
||||||
@ -530,6 +612,8 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||||||
return [DisplayCategory.CONTACT_SENSOR]
|
return [DisplayCategory.CONTACT_SENSOR]
|
||||||
if sensor_type is self.TYPE_MOTION:
|
if sensor_type is self.TYPE_MOTION:
|
||||||
return [DisplayCategory.MOTION_SENSOR]
|
return [DisplayCategory.MOTION_SENSOR]
|
||||||
|
if sensor_type is self.TYPE_PRESENCE:
|
||||||
|
return [DisplayCategory.CAMERA]
|
||||||
|
|
||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
@ -538,7 +622,10 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||||||
yield AlexaContactSensor(self.hass, self.entity)
|
yield AlexaContactSensor(self.hass, self.entity)
|
||||||
elif sensor_type is self.TYPE_MOTION:
|
elif sensor_type is self.TYPE_MOTION:
|
||||||
yield AlexaMotionSensor(self.hass, self.entity)
|
yield AlexaMotionSensor(self.hass, self.entity)
|
||||||
|
elif sensor_type is self.TYPE_PRESENCE:
|
||||||
|
yield AlexaEventDetectionSensor(self.hass, self.entity)
|
||||||
|
|
||||||
|
# yield additional interfaces based on specified display category in config.
|
||||||
entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
|
entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
|
||||||
if CONF_DISPLAY_CATEGORIES in entity_conf:
|
if CONF_DISPLAY_CATEGORIES in entity_conf:
|
||||||
if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL:
|
if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL:
|
||||||
@ -547,6 +634,8 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||||||
yield AlexaContactSensor(self.hass, self.entity)
|
yield AlexaContactSensor(self.hass, self.entity)
|
||||||
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.MOTION_SENSOR:
|
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.MOTION_SENSOR:
|
||||||
yield AlexaMotionSensor(self.hass, self.entity)
|
yield AlexaMotionSensor(self.hass, self.entity)
|
||||||
|
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.CAMERA:
|
||||||
|
yield AlexaEventDetectionSensor(self.hass, self.entity)
|
||||||
|
|
||||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.hass)
|
yield Alexa(self.hass)
|
||||||
@ -554,11 +643,20 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||||||
def get_type(self):
|
def get_type(self):
|
||||||
"""Return the type of binary sensor."""
|
"""Return the type of binary sensor."""
|
||||||
attrs = self.entity.attributes
|
attrs = self.entity.attributes
|
||||||
if attrs.get(ATTR_DEVICE_CLASS) in ("door", "garage_door", "opening", "window"):
|
if attrs.get(ATTR_DEVICE_CLASS) in (
|
||||||
|
binary_sensor.DEVICE_CLASS_DOOR,
|
||||||
|
binary_sensor.DEVICE_CLASS_GARAGE_DOOR,
|
||||||
|
binary_sensor.DEVICE_CLASS_OPENING,
|
||||||
|
binary_sensor.DEVICE_CLASS_WINDOW,
|
||||||
|
):
|
||||||
return self.TYPE_CONTACT
|
return self.TYPE_CONTACT
|
||||||
if attrs.get(ATTR_DEVICE_CLASS) == "motion":
|
|
||||||
|
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_MOTION:
|
||||||
return self.TYPE_MOTION
|
return self.TYPE_MOTION
|
||||||
|
|
||||||
|
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_PRESENCE:
|
||||||
|
return self.TYPE_PRESENCE
|
||||||
|
|
||||||
|
|
||||||
@ENTITY_ADAPTERS.register(alarm_control_panel.DOMAIN)
|
@ENTITY_ADAPTERS.register(alarm_control_panel.DOMAIN)
|
||||||
class AlarmControlPanelCapabilities(AlexaEntity):
|
class AlarmControlPanelCapabilities(AlexaEntity):
|
||||||
@ -574,3 +672,36 @@ class AlarmControlPanelCapabilities(AlexaEntity):
|
|||||||
yield AlexaSecurityPanelController(self.hass, self.entity)
|
yield AlexaSecurityPanelController(self.hass, self.entity)
|
||||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.hass)
|
yield Alexa(self.hass)
|
||||||
|
|
||||||
|
|
||||||
|
@ENTITY_ADAPTERS.register(image_processing.DOMAIN)
|
||||||
|
class ImageProcessingCapabilities(AlexaEntity):
|
||||||
|
"""Class to represent image_processing capabilities."""
|
||||||
|
|
||||||
|
def default_display_categories(self):
|
||||||
|
"""Return the display categories for this entity."""
|
||||||
|
return [DisplayCategory.CAMERA]
|
||||||
|
|
||||||
|
def interfaces(self):
|
||||||
|
"""Yield the supported interfaces."""
|
||||||
|
yield AlexaEventDetectionSensor(self.hass, self.entity)
|
||||||
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
|
yield Alexa(self.hass)
|
||||||
|
|
||||||
|
|
||||||
|
@ENTITY_ADAPTERS.register(input_number.DOMAIN)
|
||||||
|
class InputNumberCapabilities(AlexaEntity):
|
||||||
|
"""Class to represent input_number capabilities."""
|
||||||
|
|
||||||
|
def default_display_categories(self):
|
||||||
|
"""Return the display categories for this entity."""
|
||||||
|
return [DisplayCategory.OTHER]
|
||||||
|
|
||||||
|
def interfaces(self):
|
||||||
|
"""Yield the supported interfaces."""
|
||||||
|
|
||||||
|
yield AlexaRangeController(
|
||||||
|
self.entity, instance=f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}"
|
||||||
|
)
|
||||||
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
|
yield Alexa(self.hass)
|
||||||
|
@ -3,10 +3,10 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
from homeassistant.components import http
|
from homeassistant.components import http
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_MAIN_TEXT,
|
ATTR_MAIN_TEXT,
|
||||||
|
@ -3,7 +3,14 @@ import logging
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
from homeassistant import core as ha
|
from homeassistant import core as ha
|
||||||
from homeassistant.components import cover, fan, group, light, media_player
|
from homeassistant.components import (
|
||||||
|
cover,
|
||||||
|
fan,
|
||||||
|
group,
|
||||||
|
input_number,
|
||||||
|
light,
|
||||||
|
media_player,
|
||||||
|
)
|
||||||
from homeassistant.components.climate import const as climate
|
from homeassistant.components.climate import const as climate
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@ -20,6 +27,7 @@ from homeassistant.const import (
|
|||||||
SERVICE_MEDIA_PREVIOUS_TRACK,
|
SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||||
SERVICE_MEDIA_STOP,
|
SERVICE_MEDIA_STOP,
|
||||||
SERVICE_SET_COVER_POSITION,
|
SERVICE_SET_COVER_POSITION,
|
||||||
|
SERVICE_SET_COVER_TILT_POSITION,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
SERVICE_UNLOCK,
|
SERVICE_UNLOCK,
|
||||||
@ -28,26 +36,24 @@ from homeassistant.const import (
|
|||||||
SERVICE_VOLUME_SET,
|
SERVICE_VOLUME_SET,
|
||||||
SERVICE_VOLUME_UP,
|
SERVICE_VOLUME_UP,
|
||||||
STATE_ALARM_DISARMED,
|
STATE_ALARM_DISARMED,
|
||||||
STATE_CLOSED,
|
|
||||||
STATE_OPEN,
|
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util.temperature import convert as convert_temperature
|
from homeassistant.util.temperature import convert as convert_temperature
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
API_TEMP_UNITS,
|
API_TEMP_UNITS,
|
||||||
API_THERMOSTAT_MODES_CUSTOM,
|
|
||||||
API_THERMOSTAT_MODES,
|
API_THERMOSTAT_MODES,
|
||||||
|
API_THERMOSTAT_MODES_CUSTOM,
|
||||||
API_THERMOSTAT_PRESETS,
|
API_THERMOSTAT_PRESETS,
|
||||||
Cause,
|
|
||||||
Inputs,
|
|
||||||
PERCENTAGE_FAN_MAP,
|
PERCENTAGE_FAN_MAP,
|
||||||
RANGE_FAN_MAP,
|
RANGE_FAN_MAP,
|
||||||
SPEED_FAN_MAP,
|
SPEED_FAN_MAP,
|
||||||
|
Cause,
|
||||||
|
Inputs,
|
||||||
)
|
)
|
||||||
from .entities import async_get_entities
|
from .entities import async_get_entities
|
||||||
from .errors import (
|
from .errors import (
|
||||||
@ -113,9 +119,7 @@ async def async_api_turn_on(hass, config, directive, context):
|
|||||||
domain = ha.DOMAIN
|
domain = ha.DOMAIN
|
||||||
|
|
||||||
service = SERVICE_TURN_ON
|
service = SERVICE_TURN_ON
|
||||||
if domain == cover.DOMAIN:
|
if domain == media_player.DOMAIN:
|
||||||
service = cover.SERVICE_OPEN_COVER
|
|
||||||
elif domain == media_player.DOMAIN:
|
|
||||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||||
if not supported & power_features:
|
if not supported & power_features:
|
||||||
@ -141,9 +145,7 @@ async def async_api_turn_off(hass, config, directive, context):
|
|||||||
domain = ha.DOMAIN
|
domain = ha.DOMAIN
|
||||||
|
|
||||||
service = SERVICE_TURN_OFF
|
service = SERVICE_TURN_OFF
|
||||||
if entity.domain == cover.DOMAIN:
|
if domain == media_player.DOMAIN:
|
||||||
service = cover.SERVICE_CLOSE_COVER
|
|
||||||
elif domain == media_player.DOMAIN:
|
|
||||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||||
if not supported & power_features:
|
if not supported & power_features:
|
||||||
@ -348,10 +350,6 @@ async def async_api_set_percentage(hass, config, directive, context):
|
|||||||
speed = "high"
|
speed = "high"
|
||||||
data[fan.ATTR_SPEED] = speed
|
data[fan.ATTR_SPEED] = speed
|
||||||
|
|
||||||
elif entity.domain == cover.DOMAIN:
|
|
||||||
service = SERVICE_SET_COVER_POSITION
|
|
||||||
data[cover.ATTR_POSITION] = percentage
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
entity.domain, service, data, blocking=False, context=context
|
entity.domain, service, data, blocking=False, context=context
|
||||||
)
|
)
|
||||||
@ -385,13 +383,6 @@ async def async_api_adjust_percentage(hass, config, directive, context):
|
|||||||
|
|
||||||
data[fan.ATTR_SPEED] = speed
|
data[fan.ATTR_SPEED] = speed
|
||||||
|
|
||||||
elif entity.domain == cover.DOMAIN:
|
|
||||||
service = SERVICE_SET_COVER_POSITION
|
|
||||||
|
|
||||||
current = entity.attributes.get(cover.ATTR_POSITION)
|
|
||||||
|
|
||||||
data[cover.ATTR_POSITION] = max(0, percentage_delta + current)
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
entity.domain, service, data, blocking=False, context=context
|
entity.domain, service, data, blocking=False, context=context
|
||||||
)
|
)
|
||||||
@ -421,6 +412,10 @@ async def async_api_lock(hass, config, directive, context):
|
|||||||
@HANDLERS.register(("Alexa.LockController", "Unlock"))
|
@HANDLERS.register(("Alexa.LockController", "Unlock"))
|
||||||
async def async_api_unlock(hass, config, directive, context):
|
async def async_api_unlock(hass, config, directive, context):
|
||||||
"""Process an unlock request."""
|
"""Process an unlock request."""
|
||||||
|
if config.locale not in {"de-DE", "en-US", "ja-JP"}:
|
||||||
|
msg = f"The unlock directive is not supported for the following locales: {config.locale}"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
entity = directive.entity
|
entity = directive.entity
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
entity.domain,
|
entity.domain,
|
||||||
@ -960,32 +955,35 @@ async def async_api_disarm(hass, config, directive, context):
|
|||||||
|
|
||||||
@HANDLERS.register(("Alexa.ModeController", "SetMode"))
|
@HANDLERS.register(("Alexa.ModeController", "SetMode"))
|
||||||
async def async_api_set_mode(hass, config, directive, context):
|
async def async_api_set_mode(hass, config, directive, context):
|
||||||
"""Process a next request."""
|
"""Process a SetMode directive."""
|
||||||
entity = directive.entity
|
entity = directive.entity
|
||||||
instance = directive.instance
|
instance = directive.instance
|
||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
service = None
|
service = None
|
||||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
capability_mode = directive.payload["mode"]
|
mode = directive.payload["mode"]
|
||||||
|
|
||||||
if domain not in (fan.DOMAIN, cover.DOMAIN):
|
|
||||||
msg = "Entity does not support directive"
|
|
||||||
raise AlexaInvalidDirectiveError(msg)
|
|
||||||
|
|
||||||
|
# Fan Direction
|
||||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
|
if instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
|
||||||
_, direction = capability_mode.split(".")
|
_, direction = mode.split(".")
|
||||||
if direction in (fan.DIRECTION_REVERSE, fan.DIRECTION_FORWARD):
|
if direction in (fan.DIRECTION_REVERSE, fan.DIRECTION_FORWARD):
|
||||||
service = fan.SERVICE_SET_DIRECTION
|
service = fan.SERVICE_SET_DIRECTION
|
||||||
data[fan.ATTR_DIRECTION] = direction
|
data[fan.ATTR_DIRECTION] = direction
|
||||||
|
|
||||||
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
# Cover Position
|
||||||
_, position = capability_mode.split(".")
|
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||||
|
_, position = mode.split(".")
|
||||||
|
|
||||||
if position == STATE_CLOSED:
|
if position == cover.STATE_CLOSED:
|
||||||
service = cover.SERVICE_CLOSE_COVER
|
service = cover.SERVICE_CLOSE_COVER
|
||||||
|
elif position == cover.STATE_OPEN:
|
||||||
if position == STATE_OPEN:
|
|
||||||
service = cover.SERVICE_OPEN_COVER
|
service = cover.SERVICE_OPEN_COVER
|
||||||
|
elif position == "custom":
|
||||||
|
service = cover.SERVICE_STOP_COVER
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
domain, service, data, blocking=False, context=context
|
domain, service, data, blocking=False, context=context
|
||||||
@ -997,7 +995,7 @@ async def async_api_set_mode(hass, config, directive, context):
|
|||||||
"namespace": "Alexa.ModeController",
|
"namespace": "Alexa.ModeController",
|
||||||
"instance": instance,
|
"instance": instance,
|
||||||
"name": "mode",
|
"name": "mode",
|
||||||
"value": capability_mode,
|
"value": mode,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1008,24 +1006,13 @@ async def async_api_set_mode(hass, config, directive, context):
|
|||||||
async def async_api_adjust_mode(hass, config, directive, context):
|
async def async_api_adjust_mode(hass, config, directive, context):
|
||||||
"""Process a AdjustMode request.
|
"""Process a AdjustMode request.
|
||||||
|
|
||||||
Requires modeResources to be ordered.
|
Requires capabilityResources supportedModes to be ordered.
|
||||||
Only modes that are ordered support the adjustMode directive.
|
Only supportedModes with ordered=True support the adjustMode directive.
|
||||||
"""
|
"""
|
||||||
entity = directive.entity
|
|
||||||
instance = directive.instance
|
|
||||||
domain = entity.domain
|
|
||||||
|
|
||||||
if domain != fan.DOMAIN:
|
# Currently no supportedModes are configured with ordered=True to support this request.
|
||||||
msg = "Entity does not support directive"
|
msg = "Entity does not support directive"
|
||||||
raise AlexaInvalidDirectiveError(msg)
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
if instance is None:
|
|
||||||
msg = "Entity does not support directive"
|
|
||||||
raise AlexaInvalidDirectiveError(msg)
|
|
||||||
|
|
||||||
# No modeResources are currently ordered to support this request.
|
|
||||||
|
|
||||||
return directive.response()
|
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register(("Alexa.ToggleController", "TurnOn"))
|
@HANDLERS.register(("Alexa.ToggleController", "TurnOn"))
|
||||||
@ -1037,19 +1024,29 @@ async def async_api_toggle_on(hass, config, directive, context):
|
|||||||
service = None
|
service = None
|
||||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
|
|
||||||
if domain != fan.DOMAIN:
|
# Fan Oscillating
|
||||||
msg = "Entity does not support directive"
|
|
||||||
raise AlexaInvalidDirectiveError(msg)
|
|
||||||
|
|
||||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
|
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
|
||||||
service = fan.SERVICE_OSCILLATE
|
service = fan.SERVICE_OSCILLATE
|
||||||
data[fan.ATTR_OSCILLATING] = True
|
data[fan.ATTR_OSCILLATING] = True
|
||||||
|
else:
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
domain, service, data, blocking=False, context=context
|
domain, service, data, blocking=False, context=context
|
||||||
)
|
)
|
||||||
|
|
||||||
return directive.response()
|
response = directive.response()
|
||||||
|
response.add_context_property(
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.ToggleController",
|
||||||
|
"instance": instance,
|
||||||
|
"name": "toggleState",
|
||||||
|
"value": "ON",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register(("Alexa.ToggleController", "TurnOff"))
|
@HANDLERS.register(("Alexa.ToggleController", "TurnOff"))
|
||||||
@ -1061,19 +1058,29 @@ async def async_api_toggle_off(hass, config, directive, context):
|
|||||||
service = None
|
service = None
|
||||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
|
|
||||||
if domain != fan.DOMAIN:
|
# Fan Oscillating
|
||||||
msg = "Entity does not support directive"
|
|
||||||
raise AlexaInvalidDirectiveError(msg)
|
|
||||||
|
|
||||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
|
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
|
||||||
service = fan.SERVICE_OSCILLATE
|
service = fan.SERVICE_OSCILLATE
|
||||||
data[fan.ATTR_OSCILLATING] = False
|
data[fan.ATTR_OSCILLATING] = False
|
||||||
|
else:
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
domain, service, data, blocking=False, context=context
|
domain, service, data, blocking=False, context=context
|
||||||
)
|
)
|
||||||
|
|
||||||
return directive.response()
|
response = directive.response()
|
||||||
|
response.add_context_property(
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.ToggleController",
|
||||||
|
"instance": instance,
|
||||||
|
"name": "toggleState",
|
||||||
|
"value": "OFF",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register(("Alexa.RangeController", "SetRangeValue"))
|
@HANDLERS.register(("Alexa.RangeController", "SetRangeValue"))
|
||||||
@ -1084,15 +1091,12 @@ async def async_api_set_range(hass, config, directive, context):
|
|||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
service = None
|
service = None
|
||||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
range_value = int(directive.payload["rangeValue"])
|
range_value = directive.payload["rangeValue"]
|
||||||
|
|
||||||
if domain != fan.DOMAIN:
|
|
||||||
msg = "Entity does not support directive"
|
|
||||||
raise AlexaInvalidDirectiveError(msg)
|
|
||||||
|
|
||||||
|
# Fan Speed
|
||||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
|
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
|
||||||
service = fan.SERVICE_SET_SPEED
|
service = fan.SERVICE_SET_SPEED
|
||||||
speed = SPEED_FAN_MAP.get(range_value, None)
|
speed = SPEED_FAN_MAP.get(int(range_value))
|
||||||
|
|
||||||
if not speed:
|
if not speed:
|
||||||
msg = "Entity does not support value"
|
msg = "Entity does not support value"
|
||||||
@ -1103,11 +1107,55 @@ async def async_api_set_range(hass, config, directive, context):
|
|||||||
|
|
||||||
data[fan.ATTR_SPEED] = speed
|
data[fan.ATTR_SPEED] = speed
|
||||||
|
|
||||||
|
# Cover Position
|
||||||
|
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||||
|
range_value = int(range_value)
|
||||||
|
if range_value == 0:
|
||||||
|
service = cover.SERVICE_CLOSE_COVER
|
||||||
|
elif range_value == 100:
|
||||||
|
service = cover.SERVICE_OPEN_COVER
|
||||||
|
else:
|
||||||
|
service = cover.SERVICE_SET_COVER_POSITION
|
||||||
|
data[cover.ATTR_POSITION] = range_value
|
||||||
|
|
||||||
|
# Cover Tilt Position
|
||||||
|
elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
|
||||||
|
range_value = int(range_value)
|
||||||
|
if range_value == 0:
|
||||||
|
service = cover.SERVICE_CLOSE_COVER_TILT
|
||||||
|
elif range_value == 100:
|
||||||
|
service = cover.SERVICE_OPEN_COVER_TILT
|
||||||
|
else:
|
||||||
|
service = cover.SERVICE_SET_COVER_TILT_POSITION
|
||||||
|
data[cover.ATTR_POSITION] = range_value
|
||||||
|
|
||||||
|
# Input Number Value
|
||||||
|
elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
|
||||||
|
range_value = float(range_value)
|
||||||
|
service = input_number.SERVICE_SET_VALUE
|
||||||
|
min_value = float(entity.attributes[input_number.ATTR_MIN])
|
||||||
|
max_value = float(entity.attributes[input_number.ATTR_MAX])
|
||||||
|
data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
domain, service, data, blocking=False, context=context
|
domain, service, data, blocking=False, context=context
|
||||||
)
|
)
|
||||||
|
|
||||||
return directive.response()
|
response = directive.response()
|
||||||
|
response.add_context_property(
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.RangeController",
|
||||||
|
"instance": instance,
|
||||||
|
"name": "rangeValue",
|
||||||
|
"value": range_value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register(("Alexa.RangeController", "AdjustRangeValue"))
|
@HANDLERS.register(("Alexa.RangeController", "AdjustRangeValue"))
|
||||||
@ -1118,25 +1166,71 @@ async def async_api_adjust_range(hass, config, directive, context):
|
|||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
service = None
|
service = None
|
||||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
range_delta = int(directive.payload["rangeValueDelta"])
|
range_delta = directive.payload["rangeValueDelta"]
|
||||||
|
response_value = 0
|
||||||
|
|
||||||
|
# Fan Speed
|
||||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
|
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
|
||||||
|
range_delta = int(range_delta)
|
||||||
service = fan.SERVICE_SET_SPEED
|
service = fan.SERVICE_SET_SPEED
|
||||||
|
|
||||||
# adjust range
|
|
||||||
current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0)
|
current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0)
|
||||||
speed = SPEED_FAN_MAP.get(max(0, range_delta + current_range), fan.SPEED_OFF)
|
speed = SPEED_FAN_MAP.get(
|
||||||
|
min(3, max(0, range_delta + current_range)), fan.SPEED_OFF
|
||||||
|
)
|
||||||
|
|
||||||
if speed == fan.SPEED_OFF:
|
if speed == fan.SPEED_OFF:
|
||||||
service = fan.SERVICE_TURN_OFF
|
service = fan.SERVICE_TURN_OFF
|
||||||
|
|
||||||
data[fan.ATTR_SPEED] = speed
|
data[fan.ATTR_SPEED] = response_value = speed
|
||||||
|
|
||||||
|
# Cover Position
|
||||||
|
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||||
|
range_delta = int(range_delta)
|
||||||
|
service = SERVICE_SET_COVER_POSITION
|
||||||
|
current = entity.attributes.get(cover.ATTR_POSITION)
|
||||||
|
data[cover.ATTR_POSITION] = response_value = min(
|
||||||
|
100, max(0, range_delta + current)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cover Tilt Position
|
||||||
|
elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
|
||||||
|
range_delta = int(range_delta)
|
||||||
|
service = SERVICE_SET_COVER_TILT_POSITION
|
||||||
|
current = entity.attributes.get(cover.ATTR_TILT_POSITION)
|
||||||
|
data[cover.ATTR_TILT_POSITION] = response_value = min(
|
||||||
|
100, max(0, range_delta + current)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Input Number Value
|
||||||
|
elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
|
||||||
|
range_delta = float(range_delta)
|
||||||
|
service = input_number.SERVICE_SET_VALUE
|
||||||
|
min_value = float(entity.attributes[input_number.ATTR_MIN])
|
||||||
|
max_value = float(entity.attributes[input_number.ATTR_MAX])
|
||||||
|
current = float(entity.state)
|
||||||
|
data[input_number.ATTR_VALUE] = response_value = min(
|
||||||
|
max_value, max(min_value, range_delta + current)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
domain, service, data, blocking=False, context=context
|
domain, service, data, blocking=False, context=context
|
||||||
)
|
)
|
||||||
|
|
||||||
return directive.response()
|
response = directive.response()
|
||||||
|
response.add_context_property(
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.RangeController",
|
||||||
|
"instance": instance,
|
||||||
|
"name": "rangeValue",
|
||||||
|
"value": response_value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register(("Alexa.ChannelController", "ChangeChannel"))
|
@HANDLERS.register(("Alexa.ChannelController", "ChangeChannel"))
|
||||||
@ -1262,3 +1356,43 @@ async def async_api_seek(hass, config, directive, context):
|
|||||||
return directive.response(
|
return directive.response(
|
||||||
name="StateReport", namespace="Alexa.SeekController", payload=payload
|
name="StateReport", namespace="Alexa.SeekController", payload=payload
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "SetMode"))
|
||||||
|
async def async_api_set_eq_mode(hass, config, directive, context):
|
||||||
|
"""Process a SetMode request for EqualizerController."""
|
||||||
|
mode = directive.payload["mode"]
|
||||||
|
entity = directive.entity
|
||||||
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
|
|
||||||
|
sound_mode_list = entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
|
||||||
|
if sound_mode_list and mode.lower() in sound_mode_list:
|
||||||
|
data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
|
||||||
|
else:
|
||||||
|
msg = "failed to map sound mode {} to a mode on {}".format(
|
||||||
|
mode, entity.entity_id
|
||||||
|
)
|
||||||
|
raise AlexaInvalidValueError(msg)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
entity.domain,
|
||||||
|
media_player.SERVICE_SELECT_SOUND_MODE,
|
||||||
|
data,
|
||||||
|
blocking=False,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
return directive.response()
|
||||||
|
|
||||||
|
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "AdjustBands"))
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "ResetBands"))
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "SetBands"))
|
||||||
|
async def async_api_bands_directive(hass, config, directive, context):
|
||||||
|
"""Handle an AdjustBands, ResetBands, SetBands request.
|
||||||
|
|
||||||
|
Only mode directives are currently supported for the EqualizerController.
|
||||||
|
"""
|
||||||
|
# Currently bands directives are not supported.
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "alexa",
|
"domain": "alexa",
|
||||||
"name": "Alexa",
|
"name": "Amazon Alexa",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"codeowners": [
|
"codeowners": ["@home-assistant/cloud", "@ochlocracy"]
|
||||||
"@home-assistant/cloud",
|
|
||||||
"@ochlocracy"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
387
homeassistant/components/alexa/resources.py
Normal file
387
homeassistant/components/alexa/resources.py
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
"""Alexa Resources and Assets."""
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaGlobalCatalog:
|
||||||
|
"""The Global Alexa catalog.
|
||||||
|
|
||||||
|
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog
|
||||||
|
|
||||||
|
You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units.
|
||||||
|
This catalog is localized into all the languages that Alexa supports.
|
||||||
|
|
||||||
|
You can reference the following catalog of pre-defined friendly names.
|
||||||
|
Each item in the following list is an asset identifier followed by its supported friendly names.
|
||||||
|
The first friendly name for each identifier is the one displayed in the Alexa mobile app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Air Purifier, Air Cleaner,Clean Air Machine
|
||||||
|
DEVICE_NAME_AIR_PURIFIER = "Alexa.DeviceName.AirPurifier"
|
||||||
|
|
||||||
|
# Fan, Blower
|
||||||
|
DEVICE_NAME_FAN = "Alexa.DeviceName.Fan"
|
||||||
|
|
||||||
|
# Router, Internet Router, Network Router, Wifi Router, Net Router
|
||||||
|
DEVICE_NAME_ROUTER = "Alexa.DeviceName.Router"
|
||||||
|
|
||||||
|
# Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, Window shade, Interior blind
|
||||||
|
DEVICE_NAME_SHADE = "Alexa.DeviceName.Shade"
|
||||||
|
|
||||||
|
# Shower
|
||||||
|
DEVICE_NAME_SHOWER = "Alexa.DeviceName.Shower"
|
||||||
|
|
||||||
|
# Space Heater, Portable Heater
|
||||||
|
DEVICE_NAME_SPACE_HEATER = "Alexa.DeviceName.SpaceHeater"
|
||||||
|
|
||||||
|
# Washer, Washing Machine
|
||||||
|
DEVICE_NAME_WASHER = "Alexa.DeviceName.Washer"
|
||||||
|
|
||||||
|
# 2.4G Guest Wi-Fi, 2.4G Guest Network, Guest Network 2.4G, 2G Guest Wifi
|
||||||
|
SETTING_2G_GUEST_WIFI = "Alexa.Setting.2GGuestWiFi"
|
||||||
|
|
||||||
|
# 5G Guest Wi-Fi, 5G Guest Network, Guest Network 5G, 5G Guest Wifi
|
||||||
|
SETTING_5G_GUEST_WIFI = "Alexa.Setting.5GGuestWiFi"
|
||||||
|
|
||||||
|
# Auto, Automatic, Automatic Mode, Auto Mode
|
||||||
|
SETTING_AUTO = "Alexa.Setting.Auto"
|
||||||
|
|
||||||
|
# Direction
|
||||||
|
SETTING_DIRECTION = "Alexa.Setting.Direction"
|
||||||
|
|
||||||
|
# Dry Cycle, Dry Preset, Dry Setting, Dryer Cycle, Dryer Preset, Dryer Setting
|
||||||
|
SETTING_DRY_CYCLE = "Alexa.Setting.DryCycle"
|
||||||
|
|
||||||
|
# Fan Speed, Airflow speed, Wind Speed, Air speed, Air velocity
|
||||||
|
SETTING_FAN_SPEED = "Alexa.Setting.FanSpeed"
|
||||||
|
|
||||||
|
# Guest Wi-fi, Guest Network, Guest Net
|
||||||
|
SETTING_GUEST_WIFI = "Alexa.Setting.GuestWiFi"
|
||||||
|
|
||||||
|
# Heat
|
||||||
|
SETTING_HEAT = "Alexa.Setting.Heat"
|
||||||
|
|
||||||
|
# Mode
|
||||||
|
SETTING_MODE = "Alexa.Setting.Mode"
|
||||||
|
|
||||||
|
# Night, Night Mode
|
||||||
|
SETTING_NIGHT = "Alexa.Setting.Night"
|
||||||
|
|
||||||
|
# Opening, Height, Lift, Width
|
||||||
|
SETTING_OPENING = "Alexa.Setting.Opening"
|
||||||
|
|
||||||
|
# Oscillate, Swivel, Oscillation, Spin, Back and forth
|
||||||
|
SETTING_OSCILLATE = "Alexa.Setting.Oscillate"
|
||||||
|
|
||||||
|
# Preset, Setting
|
||||||
|
SETTING_PRESET = "Alexa.Setting.Preset"
|
||||||
|
|
||||||
|
# Quiet, Quiet Mode, Noiseless, Silent
|
||||||
|
SETTING_QUIET = "Alexa.Setting.Quiet"
|
||||||
|
|
||||||
|
# Temperature, Temp
|
||||||
|
SETTING_TEMPERATURE = "Alexa.Setting.Temperature"
|
||||||
|
|
||||||
|
# Wash Cycle, Wash Preset, Wash setting
|
||||||
|
SETTING_WASH_CYCLE = "Alexa.Setting.WashCycle"
|
||||||
|
|
||||||
|
# Water Temperature, Water Temp, Water Heat
|
||||||
|
SETTING_WATER_TEMPERATURE = "Alexa.Setting.WaterTemperature"
|
||||||
|
|
||||||
|
# Handheld Shower, Shower Wand, Hand Shower
|
||||||
|
SHOWER_HAND_HELD = "Alexa.Shower.HandHeld"
|
||||||
|
|
||||||
|
# Rain Head, Overhead shower, Rain Shower, Rain Spout, Rain Faucet
|
||||||
|
SHOWER_RAIN_HEAD = "Alexa.Shower.RainHead"
|
||||||
|
|
||||||
|
# Degrees, Degree
|
||||||
|
UNIT_ANGLE_DEGREES = "Alexa.Unit.Angle.Degrees"
|
||||||
|
|
||||||
|
# Radians, Radian
|
||||||
|
UNIT_ANGLE_RADIANS = "Alexa.Unit.Angle.Radians"
|
||||||
|
|
||||||
|
# Feet, Foot
|
||||||
|
UNIT_DISTANCE_FEET = "Alexa.Unit.Distance.Feet"
|
||||||
|
|
||||||
|
# Inches, Inch
|
||||||
|
UNIT_DISTANCE_INCHES = "Alexa.Unit.Distance.Inches"
|
||||||
|
|
||||||
|
# Kilometers
|
||||||
|
UNIT_DISTANCE_KILOMETERS = "Alexa.Unit.Distance.Kilometers"
|
||||||
|
|
||||||
|
# Meters, Meter, m
|
||||||
|
UNIT_DISTANCE_METERS = "Alexa.Unit.Distance.Meters"
|
||||||
|
|
||||||
|
# Miles, Mile
|
||||||
|
UNIT_DISTANCE_MILES = "Alexa.Unit.Distance.Miles"
|
||||||
|
|
||||||
|
# Yards, Yard
|
||||||
|
UNIT_DISTANCE_YARDS = "Alexa.Unit.Distance.Yards"
|
||||||
|
|
||||||
|
# Grams, Gram, g
|
||||||
|
UNIT_MASS_GRAMS = "Alexa.Unit.Mass.Grams"
|
||||||
|
|
||||||
|
# Kilograms, Kilogram, kg
|
||||||
|
UNIT_MASS_KILOGRAMS = "Alexa.Unit.Mass.Kilograms"
|
||||||
|
|
||||||
|
# Percent
|
||||||
|
UNIT_PERCENT = "Alexa.Unit.Percent"
|
||||||
|
|
||||||
|
# Celsius, Degrees Celsius, Degrees, C, Centigrade, Degrees Centigrade
|
||||||
|
UNIT_TEMPERATURE_CELSIUS = "Alexa.Unit.Temperature.Celsius"
|
||||||
|
|
||||||
|
# Degrees, Degree
|
||||||
|
UNIT_TEMPERATURE_DEGREES = "Alexa.Unit.Temperature.Degrees"
|
||||||
|
|
||||||
|
# Fahrenheit, Degrees Fahrenheit, Degrees F, Degrees, F
|
||||||
|
UNIT_TEMPERATURE_FAHRENHEIT = "Alexa.Unit.Temperature.Fahrenheit"
|
||||||
|
|
||||||
|
# Kelvin, Degrees Kelvin, Degrees K, Degrees, K
|
||||||
|
UNIT_TEMPERATURE_KELVIN = "Alexa.Unit.Temperature.Kelvin"
|
||||||
|
|
||||||
|
# Cubic Feet, Cubic Foot
|
||||||
|
UNIT_VOLUME_CUBIC_FEET = "Alexa.Unit.Volume.CubicFeet"
|
||||||
|
|
||||||
|
# Cubic Meters, Cubic Meter, Meters Cubed
|
||||||
|
UNIT_VOLUME_CUBIC_METERS = "Alexa.Unit.Volume.CubicMeters"
|
||||||
|
|
||||||
|
# Gallons, Gallon
|
||||||
|
UNIT_VOLUME_GALLONS = "Alexa.Unit.Volume.Gallons"
|
||||||
|
|
||||||
|
# Liters, Liter, L
|
||||||
|
UNIT_VOLUME_LITERS = "Alexa.Unit.Volume.Liters"
|
||||||
|
|
||||||
|
# Pints, Pint
|
||||||
|
UNIT_VOLUME_PINTS = "Alexa.Unit.Volume.Pints"
|
||||||
|
|
||||||
|
# Quarts, Quart
|
||||||
|
UNIT_VOLUME_QUARTS = "Alexa.Unit.Volume.Quarts"
|
||||||
|
|
||||||
|
# Ounces, Ounce, oz
|
||||||
|
UNIT_WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces"
|
||||||
|
|
||||||
|
# Pounds, Pound, lbs
|
||||||
|
UNIT_WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds"
|
||||||
|
|
||||||
|
# Close
|
||||||
|
VALUE_CLOSE = "Alexa.Value.Close"
|
||||||
|
|
||||||
|
# Delicates, Delicate
|
||||||
|
VALUE_DELICATE = "Alexa.Value.Delicate"
|
||||||
|
|
||||||
|
# High
|
||||||
|
VALUE_HIGH = "Alexa.Value.High"
|
||||||
|
|
||||||
|
# Low
|
||||||
|
VALUE_LOW = "Alexa.Value.Low"
|
||||||
|
|
||||||
|
# Maximum, Max
|
||||||
|
VALUE_MAXIMUM = "Alexa.Value.Maximum"
|
||||||
|
|
||||||
|
# Medium, Mid
|
||||||
|
VALUE_MEDIUM = "Alexa.Value.Medium"
|
||||||
|
|
||||||
|
# Minimum, Min
|
||||||
|
VALUE_MINIMUM = "Alexa.Value.Minimum"
|
||||||
|
|
||||||
|
# Open
|
||||||
|
VALUE_OPEN = "Alexa.Value.Open"
|
||||||
|
|
||||||
|
# Quick Wash, Fast Wash, Wash Quickly, Speed Wash
|
||||||
|
VALUE_QUICK_WASH = "Alexa.Value.QuickWash"
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaCapabilityResource:
|
||||||
|
"""Base class for Alexa capabilityResources, ModeResources, and presetResources objects.
|
||||||
|
|
||||||
|
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, labels):
|
||||||
|
"""Initialize an Alexa resource."""
|
||||||
|
self._resource_labels = []
|
||||||
|
for label in labels:
|
||||||
|
self._resource_labels.append(label)
|
||||||
|
|
||||||
|
def serialize_capability_resources(self):
|
||||||
|
"""Return capabilityResources object serialized for an API response."""
|
||||||
|
return self.serialize_labels(self._resource_labels)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def serialize_configuration():
|
||||||
|
"""Return ModeResources, PresetResources friendlyNames serialized for an API response."""
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def serialize_labels(resources):
|
||||||
|
"""Return resource label objects for friendlyNames serialized for an API response."""
|
||||||
|
labels = []
|
||||||
|
for label in resources:
|
||||||
|
if label in AlexaGlobalCatalog.__dict__.values():
|
||||||
|
label = {"@type": "asset", "value": {"assetId": label}}
|
||||||
|
else:
|
||||||
|
label = {"@type": "text", "value": {"text": label, "locale": "en-US"}}
|
||||||
|
|
||||||
|
labels.append(label)
|
||||||
|
|
||||||
|
return {"friendlyNames": labels}
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaModeResource(AlexaCapabilityResource):
|
||||||
|
"""Implements Alexa ModeResources.
|
||||||
|
|
||||||
|
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, labels, ordered=False):
|
||||||
|
"""Initialize an Alexa modeResource."""
|
||||||
|
super().__init__(labels)
|
||||||
|
self._supported_modes = []
|
||||||
|
self._mode_ordered = ordered
|
||||||
|
|
||||||
|
def add_mode(self, value, labels):
|
||||||
|
"""Add mode to the supportedModes object."""
|
||||||
|
self._supported_modes.append({"value": value, "labels": labels})
|
||||||
|
|
||||||
|
def serialize_configuration(self):
|
||||||
|
"""Return configuration for ModeResources friendlyNames serialized for an API response."""
|
||||||
|
mode_resources = []
|
||||||
|
for mode in self._supported_modes:
|
||||||
|
result = {
|
||||||
|
"value": mode["value"],
|
||||||
|
"modeResources": self.serialize_labels(mode["labels"]),
|
||||||
|
}
|
||||||
|
mode_resources.append(result)
|
||||||
|
|
||||||
|
return {"ordered": self._mode_ordered, "supportedModes": mode_resources}
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaPresetResource(AlexaCapabilityResource):
|
||||||
|
"""Implements Alexa PresetResources.
|
||||||
|
|
||||||
|
Use presetResources with RangeController to provide a set of friendlyNames for each RangeController preset.
|
||||||
|
|
||||||
|
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, labels, min_value, max_value, precision, unit=None):
|
||||||
|
"""Initialize an Alexa presetResource."""
|
||||||
|
super().__init__(labels)
|
||||||
|
self._presets = []
|
||||||
|
self._minimum_value = min_value
|
||||||
|
self._maximum_value = max_value
|
||||||
|
self._precision = precision
|
||||||
|
self._unit_of_measure = None
|
||||||
|
if unit in AlexaGlobalCatalog.__dict__.values():
|
||||||
|
self._unit_of_measure = unit
|
||||||
|
|
||||||
|
def add_preset(self, value, labels):
|
||||||
|
"""Add preset to configuration presets array."""
|
||||||
|
self._presets.append({"value": value, "labels": labels})
|
||||||
|
|
||||||
|
def serialize_configuration(self):
|
||||||
|
"""Return configuration for PresetResources friendlyNames serialized for an API response."""
|
||||||
|
configuration = {
|
||||||
|
"supportedRange": {
|
||||||
|
"minimumValue": self._minimum_value,
|
||||||
|
"maximumValue": self._maximum_value,
|
||||||
|
"precision": self._precision,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self._unit_of_measure:
|
||||||
|
configuration["unitOfMeasure"] = self._unit_of_measure
|
||||||
|
|
||||||
|
if self._presets:
|
||||||
|
preset_resources = []
|
||||||
|
for preset in self._presets:
|
||||||
|
preset_resources.append(
|
||||||
|
{
|
||||||
|
"rangeValue": preset["value"],
|
||||||
|
"presetResources": self.serialize_labels(preset["labels"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
configuration["presets"] = preset_resources
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaSemantics:
|
||||||
|
"""Class for Alexa Semantics Object.
|
||||||
|
|
||||||
|
You can optionally enable additional utterances by using semantics. When you use semantics,
|
||||||
|
you manually map the phrases "open", "close", "raise", and "lower" to directives.
|
||||||
|
|
||||||
|
Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController.
|
||||||
|
|
||||||
|
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object
|
||||||
|
"""
|
||||||
|
|
||||||
|
MAPPINGS_ACTION = "actionMappings"
|
||||||
|
MAPPINGS_STATE = "stateMappings"
|
||||||
|
|
||||||
|
ACTIONS_TO_DIRECTIVE = "ActionsToDirective"
|
||||||
|
STATES_TO_VALUE = "StatesToValue"
|
||||||
|
STATES_TO_RANGE = "StatesToRange"
|
||||||
|
|
||||||
|
ACTION_CLOSE = "Alexa.Actions.Close"
|
||||||
|
ACTION_LOWER = "Alexa.Actions.Lower"
|
||||||
|
ACTION_OPEN = "Alexa.Actions.Open"
|
||||||
|
ACTION_RAISE = "Alexa.Actions.Raise"
|
||||||
|
|
||||||
|
STATES_OPEN = "Alexa.States.Open"
|
||||||
|
STATES_CLOSED = "Alexa.States.Closed"
|
||||||
|
|
||||||
|
DIRECTIVE_RANGE_SET_VALUE = "SetRangeValue"
|
||||||
|
DIRECTIVE_RANGE_ADJUST_VALUE = "AdjustRangeValue"
|
||||||
|
DIRECTIVE_TOGGLE_TURN_ON = "TurnOn"
|
||||||
|
DIRECTIVE_TOGGLE_TURN_OFF = "TurnOff"
|
||||||
|
DIRECTIVE_MODE_SET_MODE = "SetMode"
|
||||||
|
DIRECTIVE_MODE_ADJUST_MODE = "AdjustMode"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize an Alexa modeResource."""
|
||||||
|
self._action_mappings = []
|
||||||
|
self._state_mappings = []
|
||||||
|
|
||||||
|
def _add_action_mapping(self, semantics):
|
||||||
|
"""Add action mapping between actions and interface directives."""
|
||||||
|
self._action_mappings.append(semantics)
|
||||||
|
|
||||||
|
def _add_state_mapping(self, semantics):
|
||||||
|
"""Add state mapping between states and interface directives."""
|
||||||
|
self._state_mappings.append(semantics)
|
||||||
|
|
||||||
|
def add_states_to_value(self, states, value):
|
||||||
|
"""Add StatesToValue stateMappings."""
|
||||||
|
self._add_state_mapping(
|
||||||
|
{"@type": self.STATES_TO_VALUE, "states": states, "value": value}
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_states_to_range(self, states, min_value, max_value):
|
||||||
|
"""Add StatesToRange stateMappings."""
|
||||||
|
self._add_state_mapping(
|
||||||
|
{
|
||||||
|
"@type": self.STATES_TO_RANGE,
|
||||||
|
"states": states,
|
||||||
|
"range": {"minimumValue": min_value, "maximumValue": max_value},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_action_to_directive(self, actions, directive, payload):
|
||||||
|
"""Add ActionsToDirective actionMappings."""
|
||||||
|
self._add_action_mapping(
|
||||||
|
{
|
||||||
|
"@type": self.ACTIONS_TO_DIRECTIVE,
|
||||||
|
"actions": actions,
|
||||||
|
"directive": {"name": directive, "payload": payload},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def serialize_semantics(self):
|
||||||
|
"""Return semantics object serialized for an API response."""
|
||||||
|
semantics = {}
|
||||||
|
if self._action_mappings:
|
||||||
|
semantics[self.MAPPINGS_ACTION] = self._action_mappings
|
||||||
|
if self._state_mappings:
|
||||||
|
semantics[self.MAPPINGS_STATE] = self._state_mappings
|
||||||
|
|
||||||
|
return semantics
|
@ -4,7 +4,7 @@ import logging
|
|||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
|
|
||||||
from .const import API_DIRECTIVE, API_HEADER
|
from .const import API_DIRECTIVE, API_HEADER
|
||||||
from .errors import AlexaError, AlexaBridgeUnreachableError
|
from .errors import AlexaBridgeUnreachableError, AlexaError
|
||||||
from .handlers import HANDLERS
|
from .handlers import HANDLERS
|
||||||
from .messages import AlexaDirective
|
from .messages import AlexaDirective
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user