mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
commit
4bb319e658
34
.coveragerc
34
.coveragerc
@ -5,7 +5,6 @@ omit =
|
||||
homeassistant/__main__.py
|
||||
homeassistant/helpers/signal.py
|
||||
homeassistant/helpers/typing.py
|
||||
homeassistant/monkey_patch.py
|
||||
homeassistant/scripts/*.py
|
||||
homeassistant/util/async.py
|
||||
|
||||
@ -85,7 +84,6 @@ omit =
|
||||
homeassistant/components/blockchain/sensor.py
|
||||
homeassistant/components/bloomsky/*
|
||||
homeassistant/components/bluesound/*
|
||||
homeassistant/components/bluetooth_le_tracker/device_tracker.py
|
||||
homeassistant/components/bluetooth_tracker/*
|
||||
homeassistant/components/bme280/sensor.py
|
||||
homeassistant/components/bme680/sensor.py
|
||||
@ -97,6 +95,9 @@ omit =
|
||||
homeassistant/components/broadlink/remote.py
|
||||
homeassistant/components/broadlink/sensor.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/browser/*
|
||||
homeassistant/components/brunt/cover.py
|
||||
@ -259,6 +260,9 @@ omit =
|
||||
homeassistant/components/geniushub/*
|
||||
homeassistant/components/gearbest/sensor.py
|
||||
homeassistant/components/geizhals/sensor.py
|
||||
homeassistant/components/gios/__init__.py
|
||||
homeassistant/components/gios/air_quality.py
|
||||
homeassistant/components/gios/consts.py
|
||||
homeassistant/components/github/sensor.py
|
||||
homeassistant/components/gitlab_ci/sensor.py
|
||||
homeassistant/components/gitter/sensor.py
|
||||
@ -319,7 +323,9 @@ omit =
|
||||
homeassistant/components/iaqualink/light.py
|
||||
homeassistant/components/iaqualink/sensor.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/discovery.py
|
||||
homeassistant/components/izone/__init__.py
|
||||
@ -332,6 +338,7 @@ omit =
|
||||
homeassistant/components/influxdb/sensor.py
|
||||
homeassistant/components/insteon/*
|
||||
homeassistant/components/incomfort/*
|
||||
homeassistant/components/intesishome/*
|
||||
homeassistant/components/ios/*
|
||||
homeassistant/components/iota/*
|
||||
homeassistant/components/iperf3/*
|
||||
@ -347,6 +354,7 @@ omit =
|
||||
homeassistant/components/kankun/switch.py
|
||||
homeassistant/components/keba/*
|
||||
homeassistant/components/keenetic_ndms2/device_tracker.py
|
||||
homeassistant/components/kef/*
|
||||
homeassistant/components/keyboard/*
|
||||
homeassistant/components/keyboard_remote/*
|
||||
homeassistant/components/kira/*
|
||||
@ -601,6 +609,7 @@ omit =
|
||||
homeassistant/components/sensehat/light.py
|
||||
homeassistant/components/sensehat/sensor.py
|
||||
homeassistant/components/sensibo/climate.py
|
||||
homeassistant/components/sentry/__init__.py
|
||||
homeassistant/components/serial/sensor.py
|
||||
homeassistant/components/serial_pm/sensor.py
|
||||
homeassistant/components/sesame/lock.py
|
||||
@ -610,6 +619,8 @@ omit =
|
||||
homeassistant/components/shodan/sensor.py
|
||||
homeassistant/components/sht31/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/simplisafe/__init__.py
|
||||
homeassistant/components/simplisafe/alarm_control_panel.py
|
||||
@ -654,9 +665,11 @@ omit =
|
||||
homeassistant/components/starlingbank/sensor.py
|
||||
homeassistant/components/steam_online/sensor.py
|
||||
homeassistant/components/stiebel_eltron/*
|
||||
homeassistant/components/stookalert/*
|
||||
homeassistant/components/streamlabswater/*
|
||||
homeassistant/components/suez_water/*
|
||||
homeassistant/components/supervisord/sensor.py
|
||||
homeassistant/components/surepetcare/*.py
|
||||
homeassistant/components/swiss_hydrological_data/sensor.py
|
||||
homeassistant/components/swiss_public_transport/sensor.py
|
||||
homeassistant/components/swisscom/device_tracker.py
|
||||
@ -684,7 +697,14 @@ omit =
|
||||
homeassistant/components/telnet/switch.py
|
||||
homeassistant/components/temper/sensor.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/thermoworks_smoke/sensor.py
|
||||
homeassistant/components/thethingsnetwork/*
|
||||
@ -695,6 +715,7 @@ omit =
|
||||
homeassistant/components/tikteck/light.py
|
||||
homeassistant/components/tile/device_tracker.py
|
||||
homeassistant/components/time_date/sensor.py
|
||||
homeassistant/components/tmb/sensor.py
|
||||
homeassistant/components/todoist/calendar.py
|
||||
homeassistant/components/todoist/const.py
|
||||
homeassistant/components/tof/sensor.py
|
||||
@ -704,7 +725,6 @@ omit =
|
||||
homeassistant/components/totalconnect/*
|
||||
homeassistant/components/touchline/climate.py
|
||||
homeassistant/components/tplink/device_tracker.py
|
||||
homeassistant/components/tplink/light.py
|
||||
homeassistant/components/tplink/switch.py
|
||||
homeassistant/components/tplink_lte/*
|
||||
homeassistant/components/traccar/device_tracker.py
|
||||
@ -745,11 +765,11 @@ omit =
|
||||
homeassistant/components/velbus/climate.py
|
||||
homeassistant/components/velbus/const.py
|
||||
homeassistant/components/velbus/cover.py
|
||||
homeassistant/components/velbus/light.py
|
||||
homeassistant/components/velbus/sensor.py
|
||||
homeassistant/components/velbus/switch.py
|
||||
homeassistant/components/velux/*
|
||||
homeassistant/components/venstar/climate.py
|
||||
homeassistant/components/vera/*
|
||||
homeassistant/components/verisure/*
|
||||
homeassistant/components/versasense/*
|
||||
homeassistant/components/vesync/__init__.py
|
||||
@ -759,7 +779,7 @@ omit =
|
||||
homeassistant/components/viaggiatreno/sensor.py
|
||||
homeassistant/components/vicare/*
|
||||
homeassistant/components/vivotek/camera.py
|
||||
homeassistant/components/vizio/media_player.py
|
||||
homeassistant/components/vizio/*
|
||||
homeassistant/components/vlc/media_player.py
|
||||
homeassistant/components/vlc_telnet/media_player.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",
|
||||
"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
|
||||
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
|
||||
- iOS issues should be submitted to the home-assistant-iOS repository: https://github.com/home-assistant/home-assistant-iOS/issues
|
||||
- Android issues should be submitted to the home-assistant-android repository: https://github.com/home-assistant/home-assistant-android/issues
|
||||
- Do not report issues for integrations if you are using custom integration: files in <config-dir>/custom_components
|
||||
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
||||
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
|
||||
|
@ -24,7 +24,7 @@ repos:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==4.0.1
|
||||
- pydocstyle==5.0.1
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.6.2
|
||||
@ -35,6 +35,14 @@ repos:
|
||||
- --format=custom
|
||||
- --configfile=tests/bandit.yaml
|
||||
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
|
||||
# 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
|
||||
|
@ -20,14 +20,22 @@ repos:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==4.0.1
|
||||
- pydocstyle==5.0.1
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.6.2
|
||||
hooks:
|
||||
- id: bandit
|
||||
- id: bandit
|
||||
args:
|
||||
- --quiet
|
||||
- --format=custom
|
||||
- --configfile=tests/bandit.yaml
|
||||
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
|
||||
|
||||
python:
|
||||
version: 3.6
|
||||
version: 3.7
|
||||
setup_py_install: true
|
||||
|
||||
requirements_file: requirements_docs.txt
|
||||
|
14
.travis.yml
14
.travis.yml
@ -1,9 +1,7 @@
|
||||
sudo: false
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "ppa:jonathonf/ffmpeg-4"
|
||||
packages:
|
||||
- libudev-dev
|
||||
- libavformat-dev
|
||||
@ -16,15 +14,13 @@ addons:
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "3.6.1"
|
||||
- python: "3.7.0"
|
||||
env: TOXENV=lint
|
||||
- python: "3.6.1"
|
||||
- python: "3.7.0"
|
||||
env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30
|
||||
- python: "3.6.1"
|
||||
- python: "3.7.0"
|
||||
env: TOXENV=typing
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=py36
|
||||
- python: "3.7"
|
||||
- python: "3.7.0"
|
||||
env: TOXENV=py37
|
||||
|
||||
cache:
|
||||
|
21
CODEOWNERS
21
CODEOWNERS
@ -51,6 +51,7 @@ homeassistant/components/blink/* @fronzbot
|
||||
homeassistant/components/bmw_connected_drive/* @gerard33
|
||||
homeassistant/components/braviatv/* @robbiet480
|
||||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||
homeassistant/components/brother/* @bieniu
|
||||
homeassistant/components/brunt/* @eavanvalkenburg
|
||||
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
||||
homeassistant/components/buienradar/* @mjj4791 @ties
|
||||
@ -85,6 +86,7 @@ homeassistant/components/ecobee/* @marthoc
|
||||
homeassistant/components/ecovacs/* @OverloadUT
|
||||
homeassistant/components/egardia/* @jeroenterheerdt
|
||||
homeassistant/components/eight_sleep/* @mezz64
|
||||
homeassistant/components/elgato/* @frenck
|
||||
homeassistant/components/elv/* @majuss
|
||||
homeassistant/components/emby/* @mezz64
|
||||
homeassistant/components/emulated_hue/* @NobleKangaroo
|
||||
@ -118,6 +120,7 @@ homeassistant/components/geniushub/* @zxdavb
|
||||
homeassistant/components/geo_rss_events/* @exxamalte
|
||||
homeassistant/components/geonetnz_quakes/* @exxamalte
|
||||
homeassistant/components/geonetnz_volcano/* @exxamalte
|
||||
homeassistant/components/gios/* @bieniu
|
||||
homeassistant/components/gitter/* @fabaff
|
||||
homeassistant/components/glances/* @fabaff @engrbm87
|
||||
homeassistant/components/gntp/* @robbiet480
|
||||
@ -151,6 +154,7 @@ homeassistant/components/huawei_lte/* @scop
|
||||
homeassistant/components/huawei_router/* @abmantis
|
||||
homeassistant/components/hue/* @balloob
|
||||
homeassistant/components/iaqualink/* @flz
|
||||
homeassistant/components/icloud/* @Quentame
|
||||
homeassistant/components/ign_sismologia/* @exxamalte
|
||||
homeassistant/components/incomfort/* @zxdavb
|
||||
homeassistant/components/influxdb/* @fabaff
|
||||
@ -161,6 +165,7 @@ homeassistant/components/input_select/* @home-assistant/core
|
||||
homeassistant/components/input_text/* @home-assistant/core
|
||||
homeassistant/components/integration/* @dgomes
|
||||
homeassistant/components/intent/* @home-assistant/core
|
||||
homeassistant/components/intesishome/* @jnimmo
|
||||
homeassistant/components/ios/* @robbiet480
|
||||
homeassistant/components/iperf3/* @rohankapoorcom
|
||||
homeassistant/components/ipma/* @dgomes
|
||||
@ -172,6 +177,7 @@ homeassistant/components/juicenet/* @jesserockz
|
||||
homeassistant/components/kaiterra/* @Michsior14
|
||||
homeassistant/components/keba/* @dannerph
|
||||
homeassistant/components/keenetic_ndms2/* @foxel
|
||||
homeassistant/components/kef/* @basnijholt
|
||||
homeassistant/components/keyboard_remote/* @bendavid
|
||||
homeassistant/components/knx/* @Julius2342
|
||||
homeassistant/components/kodi/* @armills
|
||||
@ -183,6 +189,7 @@ homeassistant/components/life360/* @pnbruckner
|
||||
homeassistant/components/linky/* @Quentame
|
||||
homeassistant/components/linux_battery/* @fabaff
|
||||
homeassistant/components/liveboxplaytv/* @pschmitt
|
||||
homeassistant/components/local_ip/* @issacg
|
||||
homeassistant/components/logger/* @home-assistant/core
|
||||
homeassistant/components/logi_circle/* @evanjd
|
||||
homeassistant/components/lovelace/* @home-assistant/frontend
|
||||
@ -232,6 +239,7 @@ homeassistant/components/obihai/* @dshokouhi
|
||||
homeassistant/components/ohmconnect/* @robbiet480
|
||||
homeassistant/components/ombi/* @larssont
|
||||
homeassistant/components/onboarding/* @home-assistant/core
|
||||
homeassistant/components/onewire/* @garbled1
|
||||
homeassistant/components/opentherm_gw/* @mvn23
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/openweathermap/* @fabaff
|
||||
@ -244,6 +252,7 @@ homeassistant/components/pcal9535a/* @Shulyaka
|
||||
homeassistant/components/persistent_notification/* @home-assistant/core
|
||||
homeassistant/components/philips_js/* @elupus
|
||||
homeassistant/components/pi_hole/* @fabaff @johnluetke
|
||||
homeassistant/components/pilight/* @trekky12
|
||||
homeassistant/components/plaato/* @JohNan
|
||||
homeassistant/components/plant/* @ChristianKuehnel
|
||||
homeassistant/components/plex/* @jjlawren
|
||||
@ -265,6 +274,7 @@ homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/random/* @fabaff
|
||||
homeassistant/components/repetier/* @MTrab
|
||||
homeassistant/components/rfxtrx/* @danielhiversen
|
||||
homeassistant/components/ring/* @balloob
|
||||
homeassistant/components/rmvtransport/* @cgtobi
|
||||
homeassistant/components/roomba/* @pschmitt
|
||||
homeassistant/components/saj/* @fredericvl
|
||||
@ -274,11 +284,13 @@ homeassistant/components/scrape/* @fabaff
|
||||
homeassistant/components/script/* @home-assistant/core
|
||||
homeassistant/components/sense/* @kbickar
|
||||
homeassistant/components/sensibo/* @andrey-git
|
||||
homeassistant/components/sentry/* @dcramer
|
||||
homeassistant/components/serial/* @fabaff
|
||||
homeassistant/components/seventeentrack/* @bachya
|
||||
homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/shodan/* @fabaff
|
||||
homeassistant/components/signal_messenger/* @bbernhard
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/sinch/* @bendikrb
|
||||
homeassistant/components/slide/* @ualex73
|
||||
@ -300,11 +312,13 @@ homeassistant/components/sql/* @dgomes
|
||||
homeassistant/components/starline/* @anonym-tsk
|
||||
homeassistant/components/statistics/* @fabaff
|
||||
homeassistant/components/stiebel_eltron/* @fucm
|
||||
homeassistant/components/stookalert/* @fwestenberg
|
||||
homeassistant/components/stream/* @hunterjm
|
||||
homeassistant/components/stt/* @pvizeli
|
||||
homeassistant/components/suez_water/* @ooii
|
||||
homeassistant/components/sun/* @Swamp-Ig
|
||||
homeassistant/components/supla/* @mwegrzynek
|
||||
homeassistant/components/surepetcare/* @benleb
|
||||
homeassistant/components/swiss_hydrological_data/* @fabaff
|
||||
homeassistant/components/swiss_public_transport/* @fabaff
|
||||
homeassistant/components/switchbot/* @danielhiversen
|
||||
@ -317,14 +331,15 @@ homeassistant/components/tado/* @michaelarnauts
|
||||
homeassistant/components/tahoma/* @philklei
|
||||
homeassistant/components/tautulli/* @ludeeus
|
||||
homeassistant/components/tellduslive/* @fredrike
|
||||
homeassistant/components/template/* @PhracturedBlue
|
||||
homeassistant/components/tesla/* @zabuldon
|
||||
homeassistant/components/template/* @PhracturedBlue @tetienne
|
||||
homeassistant/components/tesla/* @zabuldon @alandtse
|
||||
homeassistant/components/tfiac/* @fredrike @mellado
|
||||
homeassistant/components/thethingsnetwork/* @fabaff
|
||||
homeassistant/components/threshold/* @fabaff
|
||||
homeassistant/components/tibber/* @danielhiversen
|
||||
homeassistant/components/tile/* @bachya
|
||||
homeassistant/components/time_date/* @fabaff
|
||||
homeassistant/components/tmb/* @alemuro
|
||||
homeassistant/components/todoist/* @boralyl
|
||||
homeassistant/components/toon/* @frenck
|
||||
homeassistant/components/tplink/* @rytilahti
|
||||
@ -358,10 +373,12 @@ homeassistant/components/waqi/* @andrey-git
|
||||
homeassistant/components/watson_tts/* @rutkai
|
||||
homeassistant/components/weather/* @fabaff
|
||||
homeassistant/components/weblink/* @home-assistant/core
|
||||
homeassistant/components/webostv/* @bendavid
|
||||
homeassistant/components/websocket_api/* @home-assistant/core
|
||||
homeassistant/components/wemo/* @sqldiablo
|
||||
homeassistant/components/withings/* @vangorra
|
||||
homeassistant/components/wled/* @frenck
|
||||
homeassistant/components/workday/* @fabaff
|
||||
homeassistant/components/worldclock/* @fabaff
|
||||
homeassistant/components/wwlln/* @bachya
|
||||
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.
|
||||
|
||||
- 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).
|
||||
- Write the code for your device, notification service, sensor, or IoT thing.
|
||||
- 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.
|
||||
|
||||
## 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 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.
|
||||
|
||||
To get started:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
python3 -m pip install homeassistant
|
||||
hass --open-ui
|
||||
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.
|
||||
|
||||
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/>`__,
|
||||
|
@ -14,8 +14,6 @@ pr:
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: 36
|
||||
image: homeassistant/ci-azure:3.6
|
||||
- container: 37
|
||||
image: homeassistant/ci-azure:3.7
|
||||
repositories:
|
||||
@ -25,7 +23,7 @@ resources:
|
||||
endpoint: 'home-assistant'
|
||||
variables:
|
||||
- name: PythonMain
|
||||
value: '36'
|
||||
value: '37'
|
||||
- group: codecov
|
||||
|
||||
stages:
|
||||
@ -54,6 +52,14 @@ stages:
|
||||
. venv/bin/activate
|
||||
pre-commit run bandit --all-files
|
||||
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'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
@ -91,7 +97,7 @@ stages:
|
||||
pre-commit install-hooks --config .pre-commit-config-all.yaml
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
pre-commit run black --all-files
|
||||
pre-commit run black --all-files --show-diff-on-failure
|
||||
displayName: 'Check Black formatting'
|
||||
|
||||
- stage: 'Tests'
|
||||
@ -104,8 +110,6 @@ stages:
|
||||
strategy:
|
||||
maxParallel: 3
|
||||
matrix:
|
||||
Python36:
|
||||
python.container: '36'
|
||||
Python37:
|
||||
python.container: '37'
|
||||
container: $[ variables['python.container'] ]
|
||||
|
@ -14,7 +14,7 @@ schedules:
|
||||
always: true
|
||||
variables:
|
||||
- name: versionBuilder
|
||||
value: '6.3'
|
||||
value: '6.9'
|
||||
- group: docker
|
||||
- group: github
|
||||
- group: twine
|
||||
@ -94,7 +94,7 @@ stages:
|
||||
buildMachine: 'raspberrypi2,raspberrypi3,raspberrypi4,odroid-xu,tinker'
|
||||
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:
|
||||
- template: templates/azp-step-ha-version.yaml@azure
|
||||
- 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 warnings
|
||||
|
||||
|
||||
__licence__ = 'BSD (3 clause)'
|
||||
|
||||
|
||||
|
@ -17,11 +17,11 @@
|
||||
# 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.
|
||||
#
|
||||
import sys
|
||||
import os
|
||||
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_PACKAGE_NAME = 'homeassistant'
|
||||
|
@ -1,14 +1,14 @@
|
||||
"""Start Home Assistant."""
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
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 __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
|
||||
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant import core
|
||||
@ -16,7 +16,6 @@ if TYPE_CHECKING:
|
||||
|
||||
def set_loop() -> None:
|
||||
"""Attempt to use different loop."""
|
||||
import asyncio
|
||||
from asyncio.events import BaseDefaultEventLoopPolicy
|
||||
|
||||
if sys.platform == "win32":
|
||||
@ -56,10 +55,8 @@ def ensure_config_path(config_dir: str) -> None:
|
||||
if not os.path.isdir(config_dir):
|
||||
if config_dir != config_util.get_default_config_dir():
|
||||
print(
|
||||
(
|
||||
"Fatal Error: Specified configuration directory does "
|
||||
"not exist {} "
|
||||
).format(config_dir)
|
||||
f"Fatal Error: Specified configuration directory {config_dir} "
|
||||
"does not exist"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
@ -67,10 +64,8 @@ def ensure_config_path(config_dir: str) -> None:
|
||||
os.mkdir(config_dir)
|
||||
except OSError:
|
||||
print(
|
||||
(
|
||||
"Fatal Error: Unable to create default configuration "
|
||||
"directory {} "
|
||||
).format(config_dir)
|
||||
"Fatal Error: Unable to create default configuration "
|
||||
f"directory {config_dir}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
@ -79,11 +74,7 @@ def ensure_config_path(config_dir: str) -> None:
|
||||
try:
|
||||
os.mkdir(lib_dir)
|
||||
except OSError:
|
||||
print(
|
||||
("Fatal Error: Unable to create library " "directory {} ").format(
|
||||
lib_dir
|
||||
)
|
||||
)
|
||||
print(f"Fatal Error: Unable to create library directory {lib_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -148,7 +139,7 @@ def get_arguments() -> argparse.Namespace:
|
||||
"--log-file",
|
||||
type=str,
|
||||
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(
|
||||
"--log-no-color", action="store_true", help="Disable color logs"
|
||||
@ -217,7 +208,7 @@ def check_pid(pid_file: str) -> None:
|
||||
except OSError:
|
||||
# PID does not exist
|
||||
return
|
||||
print("Fatal Error: HomeAssistant is already running.")
|
||||
print("Fatal Error: Home Assistant is already running.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -261,7 +252,7 @@ def cmdline() -> List[str]:
|
||||
|
||||
|
||||
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
|
||||
|
||||
hass = core.HomeAssistant()
|
||||
@ -345,11 +336,6 @@ def main() -> int:
|
||||
"""Start Home Assistant."""
|
||||
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()
|
||||
|
||||
# Run a simple daemon runner process on Windows to handle restarts
|
||||
@ -383,13 +369,11 @@ def main() -> int:
|
||||
if 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:
|
||||
try_to_restart()
|
||||
|
||||
return exit_code # type: ignore
|
||||
return exit_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,21 +1,21 @@
|
||||
"""Provide an authentication layer for Home Assistant."""
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, Tuple, cast
|
||||
|
||||
import jwt
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
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 . import auth_store, models
|
||||
from .const import GROUP_ID_ADMIN
|
||||
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
|
||||
from .providers import auth_provider_from_config, AuthProvider, LoginFlow
|
||||
from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
|
||||
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
|
||||
|
||||
EVENT_USER_ADDED = "user_added"
|
||||
EVENT_USER_REMOVED = "user_removed"
|
||||
@ -67,6 +67,69 @@ async def auth_manager_from_config(
|
||||
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:
|
||||
"""Manage the authentication for Home Assistant."""
|
||||
|
||||
@ -82,9 +145,7 @@ class AuthManager:
|
||||
self._store = store
|
||||
self._providers = providers
|
||||
self._mfa_modules = mfa_modules
|
||||
self.login_flow = data_entry_flow.FlowManager(
|
||||
hass, self._async_create_login_flow, self._async_finish_login_flow
|
||||
)
|
||||
self.login_flow = AuthManagerFlowManager(hass, self)
|
||||
|
||||
@property
|
||||
def auth_providers(self) -> List[AuthProvider]:
|
||||
@ -417,50 +478,6 @@ class AuthManager:
|
||||
|
||||
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
|
||||
def _async_get_auth_provider(
|
||||
self, credentials: models.Credentials
|
||||
|
@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
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.types import PolicyType
|
||||
|
||||
|
@ -7,7 +7,7 @@ from typing import Any, Dict, Optional
|
||||
import voluptuous as vol
|
||||
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.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
@ -7,9 +7,9 @@ import voluptuous as vol
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import (
|
||||
MultiFactorAuthModule,
|
||||
MULTI_FACTOR_AUTH_MODULES,
|
||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
||||
MULTI_FACTOR_AUTH_MODULES,
|
||||
MultiFactorAuthModule,
|
||||
SetupFlow,
|
||||
)
|
||||
|
||||
|
@ -3,9 +3,9 @@
|
||||
Sending HOTP through notify service
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Optional, List
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
@ -16,9 +16,9 @@ from homeassistant.exceptions import ServiceNotFound
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import (
|
||||
MultiFactorAuthModule,
|
||||
MULTI_FACTOR_AUTH_MODULES,
|
||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
||||
MULTI_FACTOR_AUTH_MODULES,
|
||||
MultiFactorAuthModule,
|
||||
SetupFlow,
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Time-based One Time Password auth module."""
|
||||
import asyncio
|
||||
import logging
|
||||
from io import BytesIO
|
||||
import logging
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
import voluptuous as vol
|
||||
@ -10,9 +10,9 @@ from homeassistant.auth.models import User
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import (
|
||||
MultiFactorAuthModule,
|
||||
MULTI_FACTOR_AUTH_MODULES,
|
||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
|
||||
MULTI_FACTOR_AUTH_MODULES,
|
||||
MultiFactorAuthModule,
|
||||
SetupFlow,
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Auth models."""
|
||||
from datetime import datetime, timedelta
|
||||
import secrets
|
||||
from typing import Dict, List, NamedTuple, Optional
|
||||
import uuid
|
||||
|
||||
@ -9,7 +10,6 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import permissions as perm_mdl
|
||||
from .const import GROUP_ID_ADMIN
|
||||
from .util import generate_secret
|
||||
|
||||
TOKEN_TYPE_NORMAL = "normal"
|
||||
TOKEN_TYPE_SYSTEM = "system"
|
||||
@ -96,8 +96,8 @@ class RefreshToken:
|
||||
)
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
|
||||
token = attr.ib(type=str, factory=lambda: generate_secret(64))
|
||||
jwt_key = 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: secrets.token_hex(64))
|
||||
|
||||
last_used_at = attr.ib(type=Optional[datetime], 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
|
||||
|
||||
from .const import CAT_ENTITIES
|
||||
from .models import PermissionLookup
|
||||
from .types import PolicyType
|
||||
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
|
||||
from .merge import merge_policies # noqa: F401
|
||||
from .models import PermissionLookup
|
||||
from .types import PolicyType
|
||||
from .util import test_all
|
||||
|
||||
|
||||
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -4,11 +4,10 @@ from typing import Callable, Optional
|
||||
|
||||
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 .types import CategoryType, SubCategoryDict, ValueType
|
||||
|
||||
from .util import SubCatLookupType, lookup_all, compile_policy
|
||||
from .util import SubCatLookupType, compile_policy, lookup_all
|
||||
|
||||
SINGLE_ENTITY_SCHEMA = vol.Any(
|
||||
True,
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""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:
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""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}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Helpers to deal with permissions."""
|
||||
from functools import wraps
|
||||
|
||||
from typing import Callable, Dict, List, Optional, cast
|
||||
|
||||
from .const import SUBCAT_ALL
|
||||
|
@ -8,8 +8,8 @@ import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
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.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
@ -1,20 +1,18 @@
|
||||
"""Auth provider that validates credentials via an external command."""
|
||||
|
||||
from typing import Any, Dict, Optional, cast
|
||||
|
||||
import asyncio.subprocess
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, Optional, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
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
|
||||
|
||||
|
||||
CONF_COMMAND = "command"
|
||||
CONF_ARGS = "args"
|
||||
CONF_META = "meta"
|
||||
@ -78,7 +76,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
|
||||
if process.returncode != 0:
|
||||
_LOGGER.error(
|
||||
"User %r failed to authenticate, command exited " "with code %d.",
|
||||
"User %r failed to authenticate, command exited with code %d.",
|
||||
username,
|
||||
process.returncode,
|
||||
)
|
||||
|
@ -3,21 +3,18 @@ import asyncio
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
from typing import Any, Dict, List, Optional, Set, cast
|
||||
|
||||
import bcrypt
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_ID
|
||||
from homeassistant.core import callback, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, 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
|
||||
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth_provider.homeassistant"
|
||||
|
||||
@ -203,7 +200,7 @@ class Data:
|
||||
|
||||
@AUTH_PROVIDERS.register("homeassistant")
|
||||
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"
|
||||
|
||||
|
@ -5,13 +5,12 @@ from typing import Any, Dict, Optional, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
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
|
||||
|
||||
|
||||
USER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("username"): str,
|
||||
|
@ -12,9 +12,9 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
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 ..models import Credentials, UserMeta, User
|
||||
from ..models import Credentials, User, UserMeta
|
||||
|
||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||
CONF_API_PASSWORD = "api_password"
|
||||
|
@ -3,15 +3,16 @@
|
||||
It shows list of users if 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
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.core import callback
|
||||
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
|
||||
|
||||
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."""
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Optional, Dict, Set
|
||||
from typing import Any, Dict, Optional, Set
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import core, config as conf_util, config_entries, loader
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
||||
from homeassistant import config as conf_util, config_entries, core, loader
|
||||
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.util.logging import AsyncHandler
|
||||
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
||||
from homeassistant.util.yaml import clear_secret_cache
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -27,7 +31,7 @@ DATA_LOGGING = "logging"
|
||||
|
||||
DEBUGGER_INTEGRATIONS = {"ptvsd"}
|
||||
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
|
||||
LOGGING_INTEGRATIONS = {"logger", "system_log"}
|
||||
LOGGING_INTEGRATIONS = {"logger", "system_log", "sentry"}
|
||||
STAGE_1_INTEGRATIONS = {
|
||||
# To record data
|
||||
"recorder",
|
||||
@ -62,7 +66,7 @@ async def async_from_config_dict(
|
||||
hass.config.skip_pip = skip_pip
|
||||
if skip_pip:
|
||||
_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, {})
|
||||
@ -95,11 +99,14 @@ async def async_from_config_dict(
|
||||
stop = time()
|
||||
_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 = (
|
||||
"Python 3.6 support is deprecated and will "
|
||||
"be removed in the first release after December 15, 2019. Please "
|
||||
"upgrade Python to 3.7.0 or higher."
|
||||
"Support for the running Python version "
|
||||
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
|
||||
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)
|
||||
hass.components.persistent_notification.async_create(
|
||||
@ -161,7 +168,7 @@ def async_enable_logging(
|
||||
|
||||
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"
|
||||
|
||||
if not log_no_color:
|
||||
|
@ -11,7 +11,6 @@ import logging
|
||||
|
||||
from homeassistant.core import split_entity_id
|
||||
|
||||
|
||||
# mypy: allow-untyped-defs
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -12,7 +12,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Adgangskode",
|
||||
"username": "Email adresse"
|
||||
"username": "Email-adresse"
|
||||
},
|
||||
"title": "Udfyld dine Abode-loginoplysninger"
|
||||
}
|
||||
|
@ -20,14 +20,14 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
DEFAULT_CACHEDB,
|
||||
DOMAIN,
|
||||
SIGNAL_CAPTURE_IMAGE,
|
||||
SIGNAL_TRIGGER_QUICK_ACTION,
|
||||
)
|
||||
@ -170,7 +170,7 @@ async def async_unload_entry(hass, config_entry):
|
||||
|
||||
|
||||
def setup_hass_services(hass):
|
||||
"""Home assistant services."""
|
||||
"""Home Assistant services."""
|
||||
|
||||
def change_setting(call):
|
||||
"""Change an Abode system setting."""
|
||||
|
@ -10,7 +10,7 @@ from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
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"
|
||||
|
||||
|
@ -55,7 +55,7 @@ class AbodeLight(AbodeDevice, Light):
|
||||
self._device.set_color(kwargs[ATTR_HS_COLOR])
|
||||
|
||||
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
|
||||
self._device.set_level(ceil(kwargs[ATTR_BRIGHTNESS] * 99 / 255.0))
|
||||
else:
|
||||
@ -78,7 +78,7 @@ class AbodeLight(AbodeDevice, Light):
|
||||
# Abode returns 100 during device initialization and device refresh
|
||||
if brightness == 100:
|
||||
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)
|
||||
|
||||
@property
|
||||
|
@ -3,11 +3,7 @@
|
||||
"name": "Abode",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||
"requirements": [
|
||||
"abodepy==0.16.7"
|
||||
],
|
||||
"requirements": ["abodepy==0.16.7"],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@shred86"
|
||||
]
|
||||
}
|
||||
"codeowners": ["@shred86"]
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"domain": "acer_projector",
|
||||
"name": "Acer projector",
|
||||
"name": "Acer Projector",
|
||||
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
||||
"requirements": [
|
||||
"pyserial==3.1.1"
|
||||
],
|
||||
"requirements": ["pyserial==3.1.1"],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||
import logging
|
||||
import re
|
||||
import serial
|
||||
|
||||
import serial
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
||||
from homeassistant.const import (
|
||||
STATE_ON,
|
||||
STATE_OFF,
|
||||
STATE_UNKNOWN,
|
||||
CONF_NAME,
|
||||
CONF_FILENAME,
|
||||
CONF_NAME,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import re
|
||||
import telnetlib
|
||||
from collections import namedtuple
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.device_tracker import (
|
||||
DOMAIN,
|
||||
PLATFORM_SCHEMA,
|
||||
DeviceScanner,
|
||||
)
|
||||
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__)
|
||||
|
||||
|
@ -1,16 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"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.",
|
||||
"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": {
|
||||
"connection_error": "Forbindelse mislykkedes."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home, der leveres af Hass.io add-on: {addon}?",
|
||||
"title": "AdGuard Home via Hass.io add-on"
|
||||
"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-tilf\u00f8jelse"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
@ -21,8 +23,8 @@
|
||||
"username": "Brugernavn",
|
||||
"verify_ssl": "AdGuard Home bruger et korrekt certifikat"
|
||||
},
|
||||
"description": "Konfigurer din AdGuard Home instans for at tillade overv\u00e5gning og kontrol.",
|
||||
"title": "Link AdGuard Home."
|
||||
"description": "Konfigurer din AdGuard Home-instans for at tillade overv\u00e5gning og kontrol.",
|
||||
"title": "Forbind din AdGuard Home."
|
||||
}
|
||||
},
|
||||
"title": "AdGuard Home"
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}. Bitte aktualisieren Sie Ihr Hass.io AdGuard Home Add-on.",
|
||||
"adguard_home_outdated": "Diese Integration erfordert AdGuard Home {minimal_version} oder h\u00f6her, Sie haben {current_version}.",
|
||||
"existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.",
|
||||
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig."
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"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}.",
|
||||
"existing_instance_updated": "Configurazione esistente aggiornata.",
|
||||
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
|
||||
@ -11,7 +11,7 @@
|
||||
},
|
||||
"step": {
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
"step": {
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
|
@ -61,7 +61,6 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
tls=entry.data[CONF_SSL],
|
||||
verify_ssl=entry.data[CONF_VERIFY_SSL],
|
||||
loop=hass.loop,
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
@ -79,7 +79,6 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
password=user_input.get(CONF_PASSWORD),
|
||||
tls=user_input[CONF_SSL],
|
||||
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||
loop=self.hass.loop,
|
||||
session=session,
|
||||
)
|
||||
|
||||
@ -161,7 +160,6 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
self._hassio_discovery[CONF_HOST],
|
||||
port=self._hassio_discovery[CONF_PORT],
|
||||
tls=False,
|
||||
loop=self.hass.loop,
|
||||
session=session,
|
||||
)
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
{
|
||||
"domain": "adguard",
|
||||
"name": "AdGuard Home",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/adguard",
|
||||
"requirements": [
|
||||
"adguardhome==0.3.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@frenck"
|
||||
]
|
||||
}
|
||||
"domain": "adguard",
|
||||
"name": "AdGuard Home",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/adguard",
|
||||
"requirements": ["adguardhome==0.4.0"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@frenck"]
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
"Searches Safe Search Enforced",
|
||||
"AdGuard Safe Searches Enforced",
|
||||
"mdi:shield-search",
|
||||
"enforced_safesearch",
|
||||
"requests",
|
||||
|
@ -1,14 +1,13 @@
|
||||
"""Support for Automation Device Specification (ADS)."""
|
||||
import threading
|
||||
import struct
|
||||
import logging
|
||||
import ctypes
|
||||
from collections import namedtuple
|
||||
import asyncio
|
||||
from collections import namedtuple
|
||||
import ctypes
|
||||
import logging
|
||||
import struct
|
||||
import threading
|
||||
|
||||
import async_timeout
|
||||
|
||||
import pyads
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import (
|
||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE
|
||||
from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -4,25 +4,25 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
PLATFORM_SCHEMA,
|
||||
SUPPORT_OPEN,
|
||||
SUPPORT_CLOSE,
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_SET_POSITION,
|
||||
ATTR_POSITION,
|
||||
DEVICE_CLASSES_SCHEMA,
|
||||
PLATFORM_SCHEMA,
|
||||
SUPPORT_CLOSE,
|
||||
SUPPORT_OPEN,
|
||||
SUPPORT_SET_POSITION,
|
||||
SUPPORT_STOP,
|
||||
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
|
||||
|
||||
from . import (
|
||||
CONF_ADS_VAR,
|
||||
CONF_ADS_VAR_POSITION,
|
||||
DATA_ADS,
|
||||
AdsEntity,
|
||||
STATE_KEY_STATE,
|
||||
STATE_KEY_POSITION,
|
||||
STATE_KEY_STATE,
|
||||
AdsEntity,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -16,9 +16,9 @@ from . import (
|
||||
CONF_ADS_VAR,
|
||||
CONF_ADS_VAR_BRIGHTNESS,
|
||||
DATA_ADS,
|
||||
AdsEntity,
|
||||
STATE_KEY_BRIGHTNESS,
|
||||
STATE_KEY_STATE,
|
||||
AdsEntity,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"domain": "ads",
|
||||
"name": "Ads",
|
||||
"name": "ADS",
|
||||
"documentation": "https://www.home-assistant.io/integrations/ads",
|
||||
"requirements": [
|
||||
"pyads==3.0.7"
|
||||
],
|
||||
"requirements": ["pyads==3.0.7"],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . 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__)
|
||||
|
||||
|
@ -3,11 +3,11 @@ import logging
|
||||
|
||||
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
|
||||
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__)
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"domain": "aftership",
|
||||
"name": "Aftership",
|
||||
"name": "AfterShip",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aftership",
|
||||
"requirements": [
|
||||
"pyaftership==0.1.2"
|
||||
],
|
||||
"requirements": ["pyaftership==0.1.2"],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
async_add_entities([instance], True)
|
||||
|
||||
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)
|
||||
slug = call.data.get(CONF_SLUG)
|
||||
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):
|
||||
"""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]
|
||||
tracking_number = call.data[CONF_TRACKING_NUMBER]
|
||||
|
||||
|
@ -2,12 +2,12 @@
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"domain": "air_quality",
|
||||
"name": "Air quality",
|
||||
"name": "Air Quality",
|
||||
"documentation": "https://www.home-assistant.io/integrations/air_quality",
|
||||
"requirements": [],
|
||||
"dependencies": [],
|
||||
|
@ -3,7 +3,7 @@
|
||||
"error": {
|
||||
"auth": "API-n\u00f8glen er ikke korrekt.",
|
||||
"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": {
|
||||
"user": {
|
||||
@ -13,7 +13,7 @@
|
||||
"longitude": "L\u00e6ngdegrad",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
@ -10,7 +10,6 @@ import async_timeout
|
||||
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import Config, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
@ -48,9 +47,6 @@ async def async_setup_entry(hass, config_entry):
|
||||
|
||||
await airly.async_update()
|
||||
|
||||
if not airly.data:
|
||||
raise ConfigEntryNotReady()
|
||||
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly
|
||||
|
||||
hass.async_create_task(
|
||||
|
@ -1,11 +1,11 @@
|
||||
"""Support for the Airly air_quality service."""
|
||||
from homeassistant.components.air_quality import (
|
||||
AirQualityEntity,
|
||||
ATTR_AQI,
|
||||
ATTR_PM_10,
|
||||
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 (
|
||||
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):
|
||||
"""Set up Airly air_quality entity based on a config entry."""
|
||||
name = config_entry.data[CONF_NAME]
|
||||
latitude = config_entry.data[CONF_LATITUDE]
|
||||
longitude = config_entry.data[CONF_LONGITUDE]
|
||||
unique_id = f"{latitude}-{longitude}"
|
||||
|
||||
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||
|
||||
async_add_entities([AirlyAirQuality(data, name)], True)
|
||||
async_add_entities([AirlyAirQuality(data, name, unique_id)], True)
|
||||
|
||||
|
||||
def round_state(func):
|
||||
@ -56,11 +59,12 @@ def round_state(func):
|
||||
class AirlyAirQuality(AirQualityEntity):
|
||||
"""Define an Airly air quality."""
|
||||
|
||||
def __init__(self, airly, name):
|
||||
def __init__(self, airly, name, unique_id):
|
||||
"""Initialize."""
|
||||
self.airly = airly
|
||||
self.data = airly.data
|
||||
self._name = name
|
||||
self._unique_id = unique_id
|
||||
self._pm_2_5 = None
|
||||
self._pm_10 = None
|
||||
self._aqi = None
|
||||
@ -108,12 +112,12 @@ class AirlyAirQuality(AirQualityEntity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return f"{self.airly.latitude}-{self.airly.longitude}"
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return bool(self.airly.data)
|
||||
return bool(self.data)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
@ -132,7 +136,6 @@ class AirlyAirQuality(AirQualityEntity):
|
||||
|
||||
if self.airly.data:
|
||||
self.data = self.airly.data
|
||||
|
||||
self._pm_10 = self.data[ATTR_API_PM10]
|
||||
self._pm_2_5 = self.data[ATTR_API_PM25]
|
||||
self._aqi = self.data[ATTR_API_CAQI]
|
||||
self._pm_10 = self.data[ATTR_API_PM10]
|
||||
self._pm_2_5 = self.data[ATTR_API_PM25]
|
||||
self._aqi = self.data[ATTR_API_CAQI]
|
||||
|
@ -1,14 +1,14 @@
|
||||
"""Adds config flow for Airly."""
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
from airly import Airly
|
||||
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.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DEVICE_CLASS,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_NAME,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
@ -60,12 +62,16 @@ SENSOR_TYPES = {
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Airly sensor entities based on a config entry."""
|
||||
name = config_entry.data[CONF_NAME]
|
||||
latitude = config_entry.data[CONF_LATITUDE]
|
||||
longitude = config_entry.data[CONF_LONGITUDE]
|
||||
|
||||
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||
|
||||
sensors = []
|
||||
for sensor in SENSOR_TYPES:
|
||||
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)
|
||||
|
||||
|
||||
@ -84,11 +90,12 @@ def round_state(func):
|
||||
class AirlySensor(Entity):
|
||||
"""Define an Airly sensor."""
|
||||
|
||||
def __init__(self, airly, name, kind):
|
||||
def __init__(self, airly, name, kind, unique_id):
|
||||
"""Initialize."""
|
||||
self.airly = airly
|
||||
self.data = airly.data
|
||||
self._name = name
|
||||
self._unique_id = unique_id
|
||||
self.kind = kind
|
||||
self._device_class = None
|
||||
self._state = None
|
||||
@ -130,7 +137,7 @@ class AirlySensor(Entity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return f"{self.airly.latitude}-{self.airly.longitude}-{self.kind.lower()}"
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
@ -140,7 +147,7 @@ class AirlySensor(Entity):
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return bool(self.airly.data)
|
||||
return bool(self.data)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the sensor."""
|
||||
|
@ -1,12 +1,8 @@
|
||||
{
|
||||
"domain": "airvisual",
|
||||
"name": "Airvisual",
|
||||
"name": "AirVisual",
|
||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||
"requirements": [
|
||||
"pyairvisual==3.0.1"
|
||||
],
|
||||
"requirements": ["pyairvisual==3.0.1"],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@bachya"
|
||||
]
|
||||
"codeowners": ["@bachya"]
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ class AirVisualSensor(Entity):
|
||||
|
||||
@property
|
||||
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}"
|
||||
|
||||
@property
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"domain": "aladdin_connect",
|
||||
"name": "Aladdin connect",
|
||||
"name": "Aladdin Connect",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||
"requirements": [
|
||||
"aladdin_connect==0.3"
|
||||
],
|
||||
"requirements": ["aladdin_connect==0.3"],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -1,7 +1,18 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"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_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",
|
||||
"disarm": "Desarmar {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": {
|
||||
"action_type": {
|
||||
"arm_away": "Armer {entity_name} mode sortie",
|
||||
"arm_home": "Armer {entity_name} mode \u00e0 la maison",
|
||||
"arm_night": "Armer {entity_name} mode nuit",
|
||||
"arm_away": "Armer {entity_name} en mode \"sortie\"",
|
||||
"arm_home": "Armer {entity_name} en mode \"maison\"",
|
||||
"arm_night": "Armer {entity_name} en mode \"nuit\"",
|
||||
"disarm": "D\u00e9sarmer {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",
|
||||
"disarm": "{entity_name} \uacbd\ube44\ud574\uc81c",
|
||||
"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",
|
||||
"disarm": "Uitschakelen {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_type": {
|
||||
"armed_away": "{entity_name} borte sikkring ",
|
||||
"armed_home": "{entity_name} hjemme sikkring",
|
||||
"armed_night": "{entity_name} natt sikkring",
|
||||
"armed_away": "{entity_name} aktivert borte",
|
||||
"armed_home": "{entity_name} aktivert hjemme",
|
||||
"armed_night": "{entity_name} aktivert natt",
|
||||
"disarmed": "{entity_name} deaktivert",
|
||||
"triggered": "{entity_name} utl\u00f8st"
|
||||
}
|
||||
|
@ -6,6 +6,13 @@
|
||||
"arm_night": "Armar {entity_name} noite",
|
||||
"disarm": "Desarmar {entity_name}",
|
||||
"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
|
||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
make_entity_service_schema,
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
make_entity_service_schema,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"domain": "alarm_control_panel",
|
||||
"name": "Alarm control panel",
|
||||
"name": "Alarm Control Panel",
|
||||
"documentation": "https://www.home-assistant.io/integrations/alarm_control_panel",
|
||||
"requirements": [],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
"codeowners": [],
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ CONF_DEVICE_BAUD = "baudrate"
|
||||
CONF_DEVICE_PATH = "path"
|
||||
CONF_DEVICE_PORT = "port"
|
||||
CONF_DEVICE_TYPE = "type"
|
||||
CONF_AUTO_BYPASS = "autobypass"
|
||||
CONF_PANEL_DISPLAY = "panel_display"
|
||||
CONF_ZONE_NAME = "name"
|
||||
CONF_ZONE_TYPE = "type"
|
||||
@ -39,6 +40,7 @@ DEFAULT_DEVICE_PORT = 10000
|
||||
DEFAULT_DEVICE_PATH = "/dev/ttyUSB0"
|
||||
DEFAULT_DEVICE_BAUD = 115200
|
||||
|
||||
DEFAULT_AUTO_BYPASS = False
|
||||
DEFAULT_PANEL_DISPLAY = False
|
||||
|
||||
DEFAULT_ZONE_TYPE = "opening"
|
||||
@ -102,6 +104,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
vol.Optional(
|
||||
CONF_PANEL_DISPLAY, default=DEFAULT_PANEL_DISPLAY
|
||||
): cv.boolean,
|
||||
vol.Optional(CONF_AUTO_BYPASS, default=DEFAULT_AUTO_BYPASS): cv.boolean,
|
||||
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
|
||||
}
|
||||
)
|
||||
|
@ -4,8 +4,8 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanel,
|
||||
FORMAT_NUMBER,
|
||||
AlarmControlPanel,
|
||||
)
|
||||
from homeassistant.components.alarm_control_panel.const import (
|
||||
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):
|
||||
"""Set up for AlarmDecoder alarm panels."""
|
||||
device = AlarmDecoderAlarmPanel()
|
||||
device = AlarmDecoderAlarmPanel(discovery_info["autobypass"])
|
||||
add_entities([device])
|
||||
|
||||
def alarm_toggle_chime_handler(service):
|
||||
@ -66,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
||||
"""Representation of an AlarmDecoder-based alarm panel."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, auto_bypass):
|
||||
"""Initialize the alarm panel."""
|
||||
self._display = ""
|
||||
self._name = "Alarm Panel"
|
||||
@ -80,6 +80,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
||||
self._programming_mode = None
|
||||
self._ready = None
|
||||
self._zone_bypassed = None
|
||||
self._auto_bypass = auto_bypass
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
@ -158,11 +159,15 @@ class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
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")
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
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")
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"domain": "alarmdecoder",
|
||||
"name": "Alarmdecoder",
|
||||
"name": "AlarmDecoder",
|
||||
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
|
||||
"requirements": [
|
||||
"alarmdecoder==1.13.2"
|
||||
],
|
||||
"requirements": ["alarmdecoder==1.13.9"],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"domain": "alarmdotcom",
|
||||
"name": "Alarmdotcom",
|
||||
"name": "Alarm.com",
|
||||
"documentation": "https://www.home-assistant.io/integrations/alarmdotcom",
|
||||
"requirements": [
|
||||
"pyalarmdotcom==0.3.2"
|
||||
],
|
||||
"requirements": ["pyalarmdotcom==0.3.2"],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -1,30 +1,30 @@
|
||||
"""Support for repeating alerts when conditions are met."""
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_DATA,
|
||||
ATTR_MESSAGE,
|
||||
ATTR_TITLE,
|
||||
ATTR_DATA,
|
||||
DOMAIN as DOMAIN_NOTIFY,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_ENTITY_ID,
|
||||
STATE_IDLE,
|
||||
CONF_NAME,
|
||||
CONF_STATE,
|
||||
STATE_ON,
|
||||
STATE_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
SERVICE_TURN_OFF,
|
||||
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.util.dt import now
|
||||
|
||||
@ -213,7 +213,7 @@ class Alert(ToggleEntity):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""HASS need not poll these entities."""
|
||||
"""Home Assistant need not poll these entities."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
@ -4,8 +4,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/alert",
|
||||
"requirements": [],
|
||||
"dependencies": [],
|
||||
"after_dependencies": [
|
||||
"notify"
|
||||
],
|
||||
"codeowners": []
|
||||
"after_dependencies": ["notify"],
|
||||
"codeowners": [],
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
@ -3,31 +3,33 @@ import logging
|
||||
|
||||
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.helpers import config_validation as cv, entityfilter
|
||||
|
||||
from . import flash_briefings, intent, smart_home_http
|
||||
from .const import (
|
||||
CONF_AUDIO,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
CONF_DESCRIPTION,
|
||||
CONF_DISPLAY_CATEGORIES,
|
||||
CONF_DISPLAY_URL,
|
||||
CONF_ENDPOINT,
|
||||
CONF_ENTITY_CONFIG,
|
||||
CONF_FILTER,
|
||||
CONF_LOCALE,
|
||||
CONF_SUPPORTED_LOCALES,
|
||||
CONF_TEXT,
|
||||
CONF_TITLE,
|
||||
CONF_UID,
|
||||
DOMAIN,
|
||||
CONF_FILTER,
|
||||
CONF_ENTITY_CONFIG,
|
||||
CONF_DESCRIPTION,
|
||||
CONF_DISPLAY_CATEGORIES,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FLASH_BRIEFINGS = "flash_briefings"
|
||||
CONF_SMART_HOME = "smart_home"
|
||||
DEFAULT_LOCALE = "en-US"
|
||||
|
||||
ALEXA_ENTITY_SCHEMA = vol.Schema(
|
||||
{
|
||||
@ -42,6 +44,9 @@ SMART_HOME_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_ENDPOINT): cv.string,
|
||||
vol.Optional(CONF_CLIENT_ID): 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_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA},
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Support for Alexa skill auth."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import json
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
|
||||
@ -50,7 +51,7 @@ class Auth:
|
||||
"client_secret": self.client_secret,
|
||||
}
|
||||
_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),
|
||||
)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,12 @@
|
||||
"""Config helpers for Alexa."""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .state_report import async_enable_proactive_mode
|
||||
|
||||
|
||||
class AbstractConfig:
|
||||
class AbstractConfig(ABC):
|
||||
"""Hold the configuration for Alexa."""
|
||||
|
||||
_unsub_proactive_report = None
|
||||
@ -28,6 +30,11 @@ class AbstractConfig:
|
||||
"""Endpoint for report state."""
|
||||
return None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def locale(self):
|
||||
"""Return config locale."""
|
||||
|
||||
@property
|
||||
def entity_config(self):
|
||||
"""Return entity config."""
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""Constants for the Alexa integration."""
|
||||
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.climate import const as climate
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
|
||||
DOMAIN = "alexa"
|
||||
|
||||
@ -19,6 +19,7 @@ CONF_ENTITY_CONFIG = "entity_config"
|
||||
CONF_ENDPOINT = "endpoint"
|
||||
CONF_CLIENT_ID = "client_id"
|
||||
CONF_CLIENT_SECRET = "client_secret"
|
||||
CONF_LOCALE = "locale"
|
||||
|
||||
ATTR_UID = "uid"
|
||||
ATTR_UPDATE_DATE = "updateDate"
|
||||
@ -42,6 +43,20 @@ API_CHANGE = "change"
|
||||
|
||||
CONF_DESCRIPTION = "description"
|
||||
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"}
|
||||
|
||||
@ -117,163 +132,6 @@ class Cause:
|
||||
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:
|
||||
"""Valid names for the InputController.
|
||||
|
||||
@ -353,3 +211,11 @@ class Inputs:
|
||||
"video3": "VIDEO 3",
|
||||
"xbox": "XBOX",
|
||||
}
|
||||
|
||||
VALID_SOUND_MODE_MAP = {
|
||||
"movie": "MOVIE",
|
||||
"music": "MUSIC",
|
||||
"night": "NIGHT",
|
||||
"sport": "SPORT",
|
||||
"tv": "TV",
|
||||
}
|
||||
|
@ -1,7 +1,26 @@
|
||||
"""Alexa entity adapters."""
|
||||
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 (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
@ -11,27 +30,9 @@ from homeassistant.const import (
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
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 (
|
||||
Alexa,
|
||||
AlexaBrightnessController,
|
||||
@ -41,6 +42,8 @@ from .capabilities import (
|
||||
AlexaContactSensor,
|
||||
AlexaDoorbellEventSource,
|
||||
AlexaEndpointHealth,
|
||||
AlexaEqualizerController,
|
||||
AlexaEventDetectionSensor,
|
||||
AlexaInputController,
|
||||
AlexaLockController,
|
||||
AlexaModeController,
|
||||
@ -60,6 +63,7 @@ from .capabilities import (
|
||||
AlexaThermostatController,
|
||||
AlexaToggleController,
|
||||
)
|
||||
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
|
||||
|
||||
ENTITY_ADAPTERS = Registry()
|
||||
|
||||
@ -81,6 +85,9 @@ class DisplayCategory:
|
||||
# Indicates media devices with video or photo capabilities.
|
||||
CAMERA = "CAMERA"
|
||||
|
||||
# Indicates a non-mobile computer, such as a desktop computer.
|
||||
COMPUTER = "COMPUTER"
|
||||
|
||||
# Indicates an endpoint that detects and reports contact.
|
||||
CONTACT_SENSOR = "CONTACT_SENSOR"
|
||||
|
||||
@ -90,27 +97,60 @@ class DisplayCategory:
|
||||
# Indicates a doorbell.
|
||||
DOORBELL = "DOORBELL"
|
||||
|
||||
# Indicates a window covering on the outside of a structure.
|
||||
EXTERIOR_BLIND = "EXTERIOR_BLIND"
|
||||
|
||||
# Indicates a 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.
|
||||
LIGHT = "LIGHT"
|
||||
|
||||
# Indicates a microwave oven.
|
||||
MICROWAVE = "MICROWAVE"
|
||||
|
||||
# Indicates a mobile phone.
|
||||
MOBILE_PHONE = "MOBILE_PHONE"
|
||||
|
||||
# Indicates an endpoint that detects and reports motion.
|
||||
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.
|
||||
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
|
||||
# order of the state change is not important. For example a bedtime scene
|
||||
# might include turning off lights and lowering the thermostat, but the
|
||||
# order is unimportant. Applies to Scenes
|
||||
SCENE_TRIGGER = "SCENE_TRIGGER"
|
||||
|
||||
# Indicates a projector screen.
|
||||
SCREEN = "SCREEN"
|
||||
|
||||
# Indicates a security panel.
|
||||
SECURITY_PANEL = "SECURITY_PANEL"
|
||||
|
||||
@ -124,10 +164,16 @@ class DisplayCategory:
|
||||
# Indicates the endpoint is a speaker or speaker system.
|
||||
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
|
||||
# variety of devices.
|
||||
SWITCH = "SWITCH"
|
||||
|
||||
# Indicates a tablet computer.
|
||||
TABLET = "TABLET"
|
||||
|
||||
# Indicates endpoints that report the temperature only.
|
||||
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
|
||||
|
||||
@ -138,6 +184,9 @@ class DisplayCategory:
|
||||
# Indicates the endpoint is a television.
|
||||
TV = "TV"
|
||||
|
||||
# Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear.
|
||||
WEARABLE = "WEARABLE"
|
||||
|
||||
|
||||
class AlexaEntity:
|
||||
"""An adaptation of an entity, expressed in Alexa's terms.
|
||||
@ -211,16 +260,24 @@ class AlexaEntity:
|
||||
|
||||
def serialize_discovery(self):
|
||||
"""Serialize the entity for discovery."""
|
||||
return {
|
||||
result = {
|
||||
"displayCategories": self.display_categories(),
|
||||
"cookie": {},
|
||||
"endpointId": self.alexa_id(),
|
||||
"friendlyName": self.friendly_name(),
|
||||
"description": self.description(),
|
||||
"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
|
||||
def async_get_entities(hass, config) -> List[AlexaEntity]:
|
||||
@ -316,20 +373,40 @@ class CoverCapabilities(AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
"""Return the display categories for this entity."""
|
||||
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]
|
||||
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]
|
||||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
yield AlexaPowerController(self.entity)
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & cover.SUPPORT_SET_POSITION:
|
||||
yield AlexaPercentageController(self.entity)
|
||||
if supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN):
|
||||
yield AlexaRangeController(
|
||||
self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}"
|
||||
)
|
||||
elif supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN):
|
||||
yield AlexaModeController(
|
||||
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 Alexa(self.hass)
|
||||
|
||||
@ -353,6 +430,7 @@ class LightCapabilities(AlexaEntity):
|
||||
yield AlexaColorController(self.entity)
|
||||
if supported & light.SUPPORT_COLOR_TEMP:
|
||||
yield AlexaColorTemperatureController(self.entity)
|
||||
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.hass)
|
||||
|
||||
@ -368,6 +446,7 @@ class FanCapabilities(AlexaEntity):
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
yield AlexaPowerController(self.entity)
|
||||
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & fan.SUPPORT_SET_SPEED:
|
||||
yield AlexaPercentageController(self.entity)
|
||||
@ -375,7 +454,6 @@ class FanCapabilities(AlexaEntity):
|
||||
yield AlexaRangeController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
|
||||
)
|
||||
|
||||
if supported & fan.SUPPORT_OSCILLATE:
|
||||
yield AlexaToggleController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
|
||||
@ -453,6 +531,9 @@ class MediaPlayerCapabilities(AlexaEntity):
|
||||
if supported & media_player.const.SUPPORT_PLAY_MEDIA:
|
||||
yield AlexaChannelController(self.entity)
|
||||
|
||||
if supported & media_player.const.SUPPORT_SELECT_SOUND_MODE:
|
||||
yield AlexaEqualizerController(self.entity)
|
||||
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.hass)
|
||||
|
||||
@ -522,6 +603,7 @@ class BinarySensorCapabilities(AlexaEntity):
|
||||
|
||||
TYPE_CONTACT = "contact"
|
||||
TYPE_MOTION = "motion"
|
||||
TYPE_PRESENCE = "presence"
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return the display categories for this entity."""
|
||||
@ -530,6 +612,8 @@ class BinarySensorCapabilities(AlexaEntity):
|
||||
return [DisplayCategory.CONTACT_SENSOR]
|
||||
if sensor_type is self.TYPE_MOTION:
|
||||
return [DisplayCategory.MOTION_SENSOR]
|
||||
if sensor_type is self.TYPE_PRESENCE:
|
||||
return [DisplayCategory.CAMERA]
|
||||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
@ -538,7 +622,10 @@ class BinarySensorCapabilities(AlexaEntity):
|
||||
yield AlexaContactSensor(self.hass, self.entity)
|
||||
elif sensor_type is self.TYPE_MOTION:
|
||||
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, {})
|
||||
if CONF_DISPLAY_CATEGORIES in entity_conf:
|
||||
if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL:
|
||||
@ -547,6 +634,8 @@ class BinarySensorCapabilities(AlexaEntity):
|
||||
yield AlexaContactSensor(self.hass, self.entity)
|
||||
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.MOTION_SENSOR:
|
||||
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 Alexa(self.hass)
|
||||
@ -554,11 +643,20 @@ class BinarySensorCapabilities(AlexaEntity):
|
||||
def get_type(self):
|
||||
"""Return the type of binary sensor."""
|
||||
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
|
||||
if attrs.get(ATTR_DEVICE_CLASS) == "motion":
|
||||
|
||||
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_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)
|
||||
class AlarmControlPanelCapabilities(AlexaEntity):
|
||||
@ -574,3 +672,36 @@ class AlarmControlPanelCapabilities(AlexaEntity):
|
||||
yield AlexaSecurityPanelController(self.hass, self.entity)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
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 uuid
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components import http
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import (
|
||||
ATTR_MAIN_TEXT,
|
||||
|
@ -3,7 +3,14 @@ import logging
|
||||
import math
|
||||
|
||||
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.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@ -20,6 +27,7 @@ from homeassistant.const import (
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
SERVICE_MEDIA_STOP,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
SERVICE_SET_COVER_TILT_POSITION,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
SERVICE_UNLOCK,
|
||||
@ -28,26 +36,24 @@ from homeassistant.const import (
|
||||
SERVICE_VOLUME_SET,
|
||||
SERVICE_VOLUME_UP,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_CLOSED,
|
||||
STATE_OPEN,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
import homeassistant.util.color as color_util
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
|
||||
from .const import (
|
||||
API_TEMP_UNITS,
|
||||
API_THERMOSTAT_MODES_CUSTOM,
|
||||
API_THERMOSTAT_MODES,
|
||||
API_THERMOSTAT_MODES_CUSTOM,
|
||||
API_THERMOSTAT_PRESETS,
|
||||
Cause,
|
||||
Inputs,
|
||||
PERCENTAGE_FAN_MAP,
|
||||
RANGE_FAN_MAP,
|
||||
SPEED_FAN_MAP,
|
||||
Cause,
|
||||
Inputs,
|
||||
)
|
||||
from .entities import async_get_entities
|
||||
from .errors import (
|
||||
@ -113,9 +119,7 @@ async def async_api_turn_on(hass, config, directive, context):
|
||||
domain = ha.DOMAIN
|
||||
|
||||
service = SERVICE_TURN_ON
|
||||
if domain == cover.DOMAIN:
|
||||
service = cover.SERVICE_OPEN_COVER
|
||||
elif domain == media_player.DOMAIN:
|
||||
if domain == media_player.DOMAIN:
|
||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||
if not supported & power_features:
|
||||
@ -141,9 +145,7 @@ async def async_api_turn_off(hass, config, directive, context):
|
||||
domain = ha.DOMAIN
|
||||
|
||||
service = SERVICE_TURN_OFF
|
||||
if entity.domain == cover.DOMAIN:
|
||||
service = cover.SERVICE_CLOSE_COVER
|
||||
elif domain == media_player.DOMAIN:
|
||||
if domain == media_player.DOMAIN:
|
||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||
if not supported & power_features:
|
||||
@ -348,10 +350,6 @@ async def async_api_set_percentage(hass, config, directive, context):
|
||||
speed = "high"
|
||||
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(
|
||||
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
|
||||
|
||||
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(
|
||||
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"))
|
||||
async def async_api_unlock(hass, config, directive, context):
|
||||
"""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
|
||||
await hass.services.async_call(
|
||||
entity.domain,
|
||||
@ -960,32 +955,35 @@ async def async_api_disarm(hass, config, directive, context):
|
||||
|
||||
@HANDLERS.register(("Alexa.ModeController", "SetMode"))
|
||||
async def async_api_set_mode(hass, config, directive, context):
|
||||
"""Process a next request."""
|
||||
"""Process a SetMode directive."""
|
||||
entity = directive.entity
|
||||
instance = directive.instance
|
||||
domain = entity.domain
|
||||
service = None
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
capability_mode = directive.payload["mode"]
|
||||
|
||||
if domain not in (fan.DOMAIN, cover.DOMAIN):
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
mode = directive.payload["mode"]
|
||||
|
||||
# Fan 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):
|
||||
service = fan.SERVICE_SET_DIRECTION
|
||||
data[fan.ATTR_DIRECTION] = direction
|
||||
|
||||
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
_, position = capability_mode.split(".")
|
||||
# Cover Position
|
||||
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
|
||||
|
||||
if position == STATE_OPEN:
|
||||
elif position == cover.STATE_OPEN:
|
||||
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(
|
||||
domain, service, data, blocking=False, context=context
|
||||
@ -997,7 +995,7 @@ async def async_api_set_mode(hass, config, directive, context):
|
||||
"namespace": "Alexa.ModeController",
|
||||
"instance": instance,
|
||||
"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):
|
||||
"""Process a AdjustMode request.
|
||||
|
||||
Requires modeResources to be ordered.
|
||||
Only modes that are ordered support the adjustMode directive.
|
||||
Requires capabilityResources supportedModes to be ordered.
|
||||
Only supportedModes with ordered=True support the adjustMode directive.
|
||||
"""
|
||||
entity = directive.entity
|
||||
instance = directive.instance
|
||||
domain = entity.domain
|
||||
|
||||
if domain != fan.DOMAIN:
|
||||
msg = "Entity does not support directive"
|
||||
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()
|
||||
# Currently no supportedModes are configured with ordered=True to support this request.
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
|
||||
|
||||
@HANDLERS.register(("Alexa.ToggleController", "TurnOn"))
|
||||
@ -1037,19 +1024,29 @@ async def async_api_toggle_on(hass, config, directive, context):
|
||||
service = None
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
|
||||
if domain != fan.DOMAIN:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
|
||||
# Fan Oscillating
|
||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
|
||||
service = fan.SERVICE_OSCILLATE
|
||||
data[fan.ATTR_OSCILLATING] = True
|
||||
else:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
|
||||
await hass.services.async_call(
|
||||
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"))
|
||||
@ -1061,19 +1058,29 @@ async def async_api_toggle_off(hass, config, directive, context):
|
||||
service = None
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
|
||||
if domain != fan.DOMAIN:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
|
||||
# Fan Oscillating
|
||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
|
||||
service = fan.SERVICE_OSCILLATE
|
||||
data[fan.ATTR_OSCILLATING] = False
|
||||
else:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
|
||||
await hass.services.async_call(
|
||||
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"))
|
||||
@ -1084,15 +1091,12 @@ async def async_api_set_range(hass, config, directive, context):
|
||||
domain = entity.domain
|
||||
service = None
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
range_value = int(directive.payload["rangeValue"])
|
||||
|
||||
if domain != fan.DOMAIN:
|
||||
msg = "Entity does not support directive"
|
||||
raise AlexaInvalidDirectiveError(msg)
|
||||
range_value = directive.payload["rangeValue"]
|
||||
|
||||
# Fan Speed
|
||||
if instance == f"{fan.DOMAIN}.{fan.ATTR_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:
|
||||
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
|
||||
|
||||
# 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(
|
||||
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"))
|
||||
@ -1118,25 +1166,71 @@ async def async_api_adjust_range(hass, config, directive, context):
|
||||
domain = entity.domain
|
||||
service = None
|
||||
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}":
|
||||
range_delta = int(range_delta)
|
||||
service = fan.SERVICE_SET_SPEED
|
||||
|
||||
# adjust range
|
||||
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:
|
||||
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(
|
||||
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"))
|
||||
@ -1262,3 +1356,43 @@ async def async_api_seek(hass, config, directive, context):
|
||||
return directive.response(
|
||||
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",
|
||||
"name": "Alexa",
|
||||
"name": "Amazon Alexa",
|
||||
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
||||
"requirements": [],
|
||||
"dependencies": ["http"],
|
||||
"codeowners": [
|
||||
"@home-assistant/cloud",
|
||||
"@ochlocracy"
|
||||
]
|
||||
"codeowners": ["@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
|
||||
|
||||
from .const import API_DIRECTIVE, API_HEADER
|
||||
from .errors import AlexaError, AlexaBridgeUnreachableError
|
||||
from .errors import AlexaBridgeUnreachableError, AlexaError
|
||||
from .handlers import HANDLERS
|
||||
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