0.104.0
This commit is contained in:
Franck Nijhof 2020-01-15 21:38:22 +01:00 committed by GitHub
commit 4bb319e658
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3333 changed files with 44034 additions and 22032 deletions

View File

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

View File

@ -1,4 +1,3 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"name": "Home Assistant Dev",
"context": "..",

View File

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

View File

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

View File

@ -20,7 +20,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
@ -31,3 +31,11 @@ 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

View File

@ -4,7 +4,7 @@ build:
image: latest
python:
version: 3.6
version: 3.7
setup_py_install: true
requirements_file: requirements_docs.txt

View File

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

View File

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

View File

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

View File

@ -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/>`__,

View File

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

View File

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

View File

@ -8,7 +8,6 @@ Loosely based on https://github.com/astropy/astropy/pull/347
import os
import warnings
__licence__ = 'BSD (3 clause)'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,6 @@ import logging
from homeassistant.core import split_entity_id
# mypy: allow-untyped-defs
_LOGGER = logging.getLogger(__name__)

View File

@ -12,7 +12,7 @@
"user": {
"data": {
"password": "Adgangskode",
"username": "Email adresse"
"username": "Email-adresse"
},
"title": "Udfyld dine Abode-loginoplysninger"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,11 +3,7 @@
"name": "AdGuard Home",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adguard",
"requirements": [
"adguardhome==0.3.0"
],
"requirements": ["adguardhome==0.4.0"],
"dependencies": [],
"codeowners": [
"@frenck"
]
"codeowners": ["@frenck"]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"domain": "air_quality",
"name": "Air quality",
"name": "Air Quality",
"documentation": "https://www.home-assistant.io/integrations/air_quality",
"requirements": [],
"dependencies": [],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,25 +1006,14 @@ 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:
# Currently no supportedModes are configured with ordered=True to support this request.
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()
@HANDLERS.register(("Alexa.ToggleController", "TurnOn"))
async def async_api_toggle_on(hass, config, directive, context):
@ -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)

View File

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

View 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

View File

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

View File

@ -12,9 +12,10 @@ from .const import (
CONF_ENDPOINT,
CONF_ENTITY_CONFIG,
CONF_FILTER,
CONF_LOCALE,
)
from .state_report import async_enable_proactive_mode
from .smart_home import async_handle_message
from .state_report import async_enable_proactive_mode
_LOGGER = logging.getLogger(__name__)
SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home"
@ -53,6 +54,11 @@ class AlexaConfig(AbstractConfig):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG) or {}
@property
def locale(self):
"""Return config locale."""
return self._config.get(CONF_LOCALE)
def should_expose(self, entity_id):
"""If an entity should be exposed."""
return self._config[CONF_FILTER](entity_id)

Some files were not shown because too many files have changed in this diff Show More