Merge remote-tracking branch 'upstream/dev' into samsungtv_configflow

This commit is contained in:
escoand
2020-01-02 20:38:31 +01:00
2910 changed files with 43666 additions and 18477 deletions

View File

@@ -5,7 +5,6 @@ omit =
homeassistant/__main__.py homeassistant/__main__.py
homeassistant/helpers/signal.py homeassistant/helpers/signal.py
homeassistant/helpers/typing.py homeassistant/helpers/typing.py
homeassistant/monkey_patch.py
homeassistant/scripts/*.py homeassistant/scripts/*.py
homeassistant/util/async.py homeassistant/util/async.py
@@ -62,6 +61,7 @@ omit =
homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_cdr/mailbox.py
homeassistant/components/asterisk_mbox/* homeassistant/components/asterisk_mbox/*
homeassistant/components/asuswrt/device_tracker.py homeassistant/components/asuswrt/device_tracker.py
homeassistant/components/aten_pe/*
homeassistant/components/atome/* homeassistant/components/atome/*
homeassistant/components/august/* homeassistant/components/august/*
homeassistant/components/aurora_abb_powerone/sensor.py homeassistant/components/aurora_abb_powerone/sensor.py
@@ -83,9 +83,9 @@ omit =
homeassistant/components/blinkt/light.py homeassistant/components/blinkt/light.py
homeassistant/components/blockchain/sensor.py homeassistant/components/blockchain/sensor.py
homeassistant/components/bloomsky/* homeassistant/components/bloomsky/*
homeassistant/components/bluesound/media_player.py homeassistant/components/bluesound/*
homeassistant/components/bluetooth_le_tracker/device_tracker.py homeassistant/components/bluetooth_le_tracker/device_tracker.py
homeassistant/components/bluetooth_tracker/device_tracker.py homeassistant/components/bluetooth_tracker/*
homeassistant/components/bme280/sensor.py homeassistant/components/bme280/sensor.py
homeassistant/components/bme680/sensor.py homeassistant/components/bme680/sensor.py
homeassistant/components/bmw_connected_drive/* homeassistant/components/bmw_connected_drive/*
@@ -93,6 +93,7 @@ omit =
homeassistant/components/bom/sensor.py homeassistant/components/bom/sensor.py
homeassistant/components/bom/weather.py homeassistant/components/bom/weather.py
homeassistant/components/braviatv/media_player.py homeassistant/components/braviatv/media_player.py
homeassistant/components/broadlink/remote.py
homeassistant/components/broadlink/sensor.py homeassistant/components/broadlink/sensor.py
homeassistant/components/broadlink/switch.py homeassistant/components/broadlink/switch.py
homeassistant/components/brottsplatskartan/sensor.py homeassistant/components/brottsplatskartan/sensor.py
@@ -109,7 +110,7 @@ omit =
homeassistant/components/cast/* homeassistant/components/cast/*
homeassistant/components/cert_expiry/sensor.py homeassistant/components/cert_expiry/sensor.py
homeassistant/components/cert_expiry/helper.py homeassistant/components/cert_expiry/helper.py
homeassistant/components/channels/media_player.py homeassistant/components/channels/*
homeassistant/components/cisco_ios/device_tracker.py homeassistant/components/cisco_ios/device_tracker.py
homeassistant/components/cisco_mobility_express/device_tracker.py homeassistant/components/cisco_mobility_express/device_tracker.py
homeassistant/components/cisco_webex_teams/notify.py homeassistant/components/cisco_webex_teams/notify.py
@@ -163,6 +164,7 @@ omit =
homeassistant/components/doorbird/* homeassistant/components/doorbird/*
homeassistant/components/dovado/* homeassistant/components/dovado/*
homeassistant/components/downloader/* homeassistant/components/downloader/*
homeassistant/components/dsmr_reader/*
homeassistant/components/dte_energy_bridge/sensor.py homeassistant/components/dte_energy_bridge/sensor.py
homeassistant/components/dublin_bus_transport/sensor.py homeassistant/components/dublin_bus_transport/sensor.py
homeassistant/components/duke_energy/sensor.py homeassistant/components/duke_energy/sensor.py
@@ -178,7 +180,7 @@ omit =
homeassistant/components/ecobee/notify.py homeassistant/components/ecobee/notify.py
homeassistant/components/ecobee/sensor.py homeassistant/components/ecobee/sensor.py
homeassistant/components/ecobee/weather.py homeassistant/components/ecobee/weather.py
homeassistant/components/econet/water_heater.py homeassistant/components/econet/*
homeassistant/components/ecovacs/* homeassistant/components/ecovacs/*
homeassistant/components/eddystone_temperature/sensor.py homeassistant/components/eddystone_temperature/sensor.py
homeassistant/components/edimax/switch.py homeassistant/components/edimax/switch.py
@@ -199,6 +201,7 @@ omit =
homeassistant/components/envirophat/sensor.py homeassistant/components/envirophat/sensor.py
homeassistant/components/envisalink/* homeassistant/components/envisalink/*
homeassistant/components/ephember/climate.py homeassistant/components/ephember/climate.py
homeassistant/components/epson/const.py
homeassistant/components/epson/media_player.py homeassistant/components/epson/media_player.py
homeassistant/components/epsonworkforce/sensor.py homeassistant/components/epsonworkforce/sensor.py
homeassistant/components/eq3btsmart/climate.py homeassistant/components/eq3btsmart/climate.py
@@ -229,6 +232,7 @@ omit =
homeassistant/components/flexit/climate.py homeassistant/components/flexit/climate.py
homeassistant/components/flic/binary_sensor.py homeassistant/components/flic/binary_sensor.py
homeassistant/components/flock/notify.py homeassistant/components/flock/notify.py
homeassistant/components/flume/*
homeassistant/components/flunearyou/sensor.py homeassistant/components/flunearyou/sensor.py
homeassistant/components/flux_led/light.py homeassistant/components/flux_led/light.py
homeassistant/components/folder/sensor.py homeassistant/components/folder/sensor.py
@@ -254,6 +258,9 @@ omit =
homeassistant/components/geniushub/* homeassistant/components/geniushub/*
homeassistant/components/gearbest/sensor.py homeassistant/components/gearbest/sensor.py
homeassistant/components/geizhals/sensor.py homeassistant/components/geizhals/sensor.py
homeassistant/components/gios/__init__.py
homeassistant/components/gios/air_quality.py
homeassistant/components/gios/consts.py
homeassistant/components/github/sensor.py homeassistant/components/github/sensor.py
homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitlab_ci/sensor.py
homeassistant/components/gitter/sensor.py homeassistant/components/gitter/sensor.py
@@ -282,7 +289,7 @@ omit =
homeassistant/components/hangouts/hangouts_bot.py homeassistant/components/hangouts/hangouts_bot.py
homeassistant/components/hangouts/hangups_utils.py homeassistant/components/hangouts/hangups_utils.py
homeassistant/components/harman_kardon_avr/media_player.py homeassistant/components/harman_kardon_avr/media_player.py
homeassistant/components/harmony/remote.py homeassistant/components/harmony/*
homeassistant/components/haveibeenpwned/sensor.py homeassistant/components/haveibeenpwned/sensor.py
homeassistant/components/hdmi_cec/* homeassistant/components/hdmi_cec/*
homeassistant/components/heatmiser/climate.py homeassistant/components/heatmiser/climate.py
@@ -314,7 +321,9 @@ omit =
homeassistant/components/iaqualink/light.py homeassistant/components/iaqualink/light.py
homeassistant/components/iaqualink/sensor.py homeassistant/components/iaqualink/sensor.py
homeassistant/components/iaqualink/switch.py homeassistant/components/iaqualink/switch.py
homeassistant/components/icloud/__init__.py
homeassistant/components/icloud/device_tracker.py homeassistant/components/icloud/device_tracker.py
homeassistant/components/icloud/sensor.py
homeassistant/components/izone/climate.py homeassistant/components/izone/climate.py
homeassistant/components/izone/discovery.py homeassistant/components/izone/discovery.py
homeassistant/components/izone/__init__.py homeassistant/components/izone/__init__.py
@@ -327,6 +336,7 @@ omit =
homeassistant/components/influxdb/sensor.py homeassistant/components/influxdb/sensor.py
homeassistant/components/insteon/* homeassistant/components/insteon/*
homeassistant/components/incomfort/* homeassistant/components/incomfort/*
homeassistant/components/intesishome/*
homeassistant/components/ios/* homeassistant/components/ios/*
homeassistant/components/iota/* homeassistant/components/iota/*
homeassistant/components/iperf3/* homeassistant/components/iperf3/*
@@ -410,6 +420,7 @@ omit =
homeassistant/components/miflora/sensor.py homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/* homeassistant/components/mikrotik/*
homeassistant/components/mill/climate.py homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py
homeassistant/components/minio/* homeassistant/components/minio/*
homeassistant/components/mitemp_bt/sensor.py homeassistant/components/mitemp_bt/sensor.py
homeassistant/components/mjpeg/camera.py homeassistant/components/mjpeg/camera.py
@@ -526,6 +537,7 @@ omit =
homeassistant/components/proliphix/climate.py homeassistant/components/proliphix/climate.py
homeassistant/components/prometheus/* homeassistant/components/prometheus/*
homeassistant/components/prowl/notify.py homeassistant/components/prowl/notify.py
homeassistant/components/proxmoxve/*
homeassistant/components/proxy/camera.py homeassistant/components/proxy/camera.py
homeassistant/components/ptvsd/* homeassistant/components/ptvsd/*
homeassistant/components/pulseaudio_loopback/switch.py homeassistant/components/pulseaudio_loopback/switch.py
@@ -603,6 +615,8 @@ omit =
homeassistant/components/shodan/sensor.py homeassistant/components/shodan/sensor.py
homeassistant/components/sht31/sensor.py homeassistant/components/sht31/sensor.py
homeassistant/components/sigfox/sensor.py homeassistant/components/sigfox/sensor.py
homeassistant/components/signal_messenger/__init__.py
homeassistant/components/signal_messenger/notify.py
homeassistant/components/simplepush/notify.py homeassistant/components/simplepush/notify.py
homeassistant/components/simplisafe/__init__.py homeassistant/components/simplisafe/__init__.py
homeassistant/components/simplisafe/alarm_control_panel.py homeassistant/components/simplisafe/alarm_control_panel.py
@@ -634,7 +648,7 @@ omit =
homeassistant/components/somfy/* homeassistant/components/somfy/*
homeassistant/components/somfy_mylink/* homeassistant/components/somfy_mylink/*
homeassistant/components/sonarr/sensor.py homeassistant/components/sonarr/sensor.py
homeassistant/components/songpal/media_player.py homeassistant/components/songpal/*
homeassistant/components/sonos/* homeassistant/components/sonos/*
homeassistant/components/sony_projector/switch.py homeassistant/components/sony_projector/switch.py
homeassistant/components/spc/* homeassistant/components/spc/*
@@ -642,7 +656,8 @@ omit =
homeassistant/components/spider/* homeassistant/components/spider/*
homeassistant/components/spotcrime/sensor.py homeassistant/components/spotcrime/sensor.py
homeassistant/components/spotify/media_player.py homeassistant/components/spotify/media_player.py
homeassistant/components/squeezebox/media_player.py homeassistant/components/squeezebox/*
homeassistant/components/starline/*
homeassistant/components/starlingbank/sensor.py homeassistant/components/starlingbank/sensor.py
homeassistant/components/steam_online/sensor.py homeassistant/components/steam_online/sensor.py
homeassistant/components/stiebel_eltron/* homeassistant/components/stiebel_eltron/*
@@ -676,7 +691,14 @@ omit =
homeassistant/components/telnet/switch.py homeassistant/components/telnet/switch.py
homeassistant/components/temper/sensor.py homeassistant/components/temper/sensor.py
homeassistant/components/tensorflow/image_processing.py homeassistant/components/tensorflow/image_processing.py
homeassistant/components/tesla/* homeassistant/components/tesla/__init__.py
homeassistant/components/tesla/binary_sensor.py
homeassistant/components/tesla/climate.py
homeassistant/components/tesla/const.py
homeassistant/components/tesla/device_tracker.py
homeassistant/components/tesla/lock.py
homeassistant/components/tesla/sensor.py
homeassistant/components/tesla/switch.py
homeassistant/components/tfiac/climate.py homeassistant/components/tfiac/climate.py
homeassistant/components/thermoworks_smoke/sensor.py homeassistant/components/thermoworks_smoke/sensor.py
homeassistant/components/thethingsnetwork/* homeassistant/components/thethingsnetwork/*
@@ -688,6 +710,7 @@ omit =
homeassistant/components/tile/device_tracker.py homeassistant/components/tile/device_tracker.py
homeassistant/components/time_date/sensor.py homeassistant/components/time_date/sensor.py
homeassistant/components/todoist/calendar.py homeassistant/components/todoist/calendar.py
homeassistant/components/todoist/const.py
homeassistant/components/tof/sensor.py homeassistant/components/tof/sensor.py
homeassistant/components/tomato/device_tracker.py homeassistant/components/tomato/device_tracker.py
homeassistant/components/toon/* homeassistant/components/toon/*
@@ -695,7 +718,6 @@ omit =
homeassistant/components/totalconnect/* homeassistant/components/totalconnect/*
homeassistant/components/touchline/climate.py homeassistant/components/touchline/climate.py
homeassistant/components/tplink/device_tracker.py homeassistant/components/tplink/device_tracker.py
homeassistant/components/tplink/light.py
homeassistant/components/tplink/switch.py homeassistant/components/tplink/switch.py
homeassistant/components/tplink_lte/* homeassistant/components/tplink_lte/*
homeassistant/components/traccar/device_tracker.py homeassistant/components/traccar/device_tracker.py
@@ -736,12 +758,13 @@ omit =
homeassistant/components/velbus/climate.py homeassistant/components/velbus/climate.py
homeassistant/components/velbus/const.py homeassistant/components/velbus/const.py
homeassistant/components/velbus/cover.py homeassistant/components/velbus/cover.py
homeassistant/components/velbus/light.py
homeassistant/components/velbus/sensor.py homeassistant/components/velbus/sensor.py
homeassistant/components/velbus/switch.py homeassistant/components/velbus/switch.py
homeassistant/components/velux/* homeassistant/components/velux/*
homeassistant/components/venstar/climate.py homeassistant/components/venstar/climate.py
homeassistant/components/vera/*
homeassistant/components/verisure/* homeassistant/components/verisure/*
homeassistant/components/versasense/*
homeassistant/components/vesync/__init__.py homeassistant/components/vesync/__init__.py
homeassistant/components/vesync/common.py homeassistant/components/vesync/common.py
homeassistant/components/vesync/const.py homeassistant/components/vesync/const.py
@@ -763,7 +786,6 @@ omit =
homeassistant/components/waze_travel_time/sensor.py homeassistant/components/waze_travel_time/sensor.py
homeassistant/components/webostv/* homeassistant/components/webostv/*
homeassistant/components/wemo/* homeassistant/components/wemo/*
homeassistant/components/wemo/fan.py
homeassistant/components/whois/sensor.py homeassistant/components/whois/sensor.py
homeassistant/components/wink/* homeassistant/components/wink/*
homeassistant/components/wirelesstag/* homeassistant/components/wirelesstag/*
@@ -783,7 +805,6 @@ omit =
homeassistant/components/xmpp/notify.py homeassistant/components/xmpp/notify.py
homeassistant/components/xs1/* homeassistant/components/xs1/*
homeassistant/components/yale_smart_alarm/alarm_control_panel.py homeassistant/components/yale_smart_alarm/alarm_control_panel.py
homeassistant/components/yamaha/media_player.py
homeassistant/components/yamaha_musiccast/media_player.py homeassistant/components/yamaha_musiccast/media_player.py
homeassistant/components/yandex_transport/* homeassistant/components/yandex_transport/*
homeassistant/components/yeelight/* homeassistant/components/yeelight/*

View File

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

View File

@@ -18,14 +18,31 @@ repos:
- --safe - --safe
- --quiet - --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$ files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/PyCQA/flake8
rev: 3.7.9 rev: 3.7.9
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
- flake8-docstrings==1.5.0 - flake8-docstrings==1.5.0
- pydocstyle==4.0.1 - pydocstyle==5.0.1
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.6.2
hooks:
- id: bandit
args:
- --quiet
- --format=custom
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-json
# Using a local "system" mypy instead of the mypy hook, because its # Using a local "system" mypy instead of the mypy hook, because its
# results depend on what is installed. And the mypy hook runs in a # results depend on what is installed. And the mypy hook runs in a
# virtualenv of its own, meaning we'd need to install and maintain # virtualenv of its own, meaning we'd need to install and maintain

View File

@@ -20,5 +20,22 @@ repos:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
- flake8-docstrings==1.5.0 - flake8-docstrings==1.5.0
- pydocstyle==4.0.1 - pydocstyle==5.0.1
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.6.2
hooks:
- id: bandit
args:
- --quiet
- --format=custom
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-json

View File

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

View File

@@ -1,9 +1,7 @@
sudo: false sudo: false
dist: xenial dist: bionic
addons: addons:
apt: apt:
sources:
- sourceline: "ppa:jonathonf/ffmpeg-4"
packages: packages:
- libudev-dev - libudev-dev
- libavformat-dev - libavformat-dev
@@ -16,15 +14,13 @@ addons:
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- python: "3.6.1" - python: "3.7.0"
env: TOXENV=lint env: TOXENV=lint
- python: "3.6.1" - python: "3.7.0"
env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30 env: TOXENV=pylint PYLINT_ARGS=--jobs=0 TRAVIS_WAIT=30
- python: "3.6.1" - python: "3.7.0"
env: TOXENV=typing env: TOXENV=typing
- python: "3.6.1" - python: "3.7.0"
env: TOXENV=py36
- python: "3.7"
env: TOXENV=py37 env: TOXENV=py37
cache: cache:

View File

@@ -17,7 +17,6 @@ homeassistant/components/abode/* @shred86
homeassistant/components/adguard/* @frenck homeassistant/components/adguard/* @frenck
homeassistant/components/airly/* @bieniu homeassistant/components/airly/* @bieniu
homeassistant/components/airvisual/* @bachya homeassistant/components/airvisual/* @bachya
homeassistant/components/alarm_control_panel/* @colinodell
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
homeassistant/components/almond/* @gcampax @balloob homeassistant/components/almond/* @gcampax @balloob
homeassistant/components/alpha_vantage/* @fabaff homeassistant/components/alpha_vantage/* @fabaff
@@ -33,6 +32,7 @@ homeassistant/components/arcam_fmj/* @elupus
homeassistant/components/arduino/* @fabaff homeassistant/components/arduino/* @fabaff
homeassistant/components/arest/* @fabaff homeassistant/components/arest/* @fabaff
homeassistant/components/asuswrt/* @kennedyshead homeassistant/components/asuswrt/* @kennedyshead
homeassistant/components/aten_pe/* @mtdcr
homeassistant/components/atome/* @baqs homeassistant/components/atome/* @baqs
homeassistant/components/aurora_abb_powerone/* @davet2001 homeassistant/components/aurora_abb_powerone/* @davet2001
homeassistant/components/auth/* @home-assistant/core homeassistant/components/auth/* @home-assistant/core
@@ -50,7 +50,7 @@ homeassistant/components/bizkaibus/* @UgaitzEtxebarria
homeassistant/components/blink/* @fronzbot homeassistant/components/blink/* @fronzbot
homeassistant/components/bmw_connected_drive/* @gerard33 homeassistant/components/bmw_connected_drive/* @gerard33
homeassistant/components/braviatv/* @robbiet480 homeassistant/components/braviatv/* @robbiet480
homeassistant/components/broadlink/* @danielhiversen homeassistant/components/broadlink/* @danielhiversen @felipediel
homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/brunt/* @eavanvalkenburg
homeassistant/components/bt_smarthub/* @jxwolstenholme homeassistant/components/bt_smarthub/* @jxwolstenholme
homeassistant/components/buienradar/* @mjj4791 @ties homeassistant/components/buienradar/* @mjj4791 @ties
@@ -79,15 +79,19 @@ homeassistant/components/device_automation/* @home-assistant/core
homeassistant/components/digital_ocean/* @fabaff homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/discogs/* @thibmaek homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7 homeassistant/components/doorbird/* @oblogic7
homeassistant/components/dsmr_reader/* @depl0y
homeassistant/components/dweet/* @fabaff homeassistant/components/dweet/* @fabaff
homeassistant/components/ecobee/* @marthoc homeassistant/components/ecobee/* @marthoc
homeassistant/components/ecovacs/* @OverloadUT homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/egardia/* @jeroenterheerdt homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/elgato/* @frenck
homeassistant/components/elv/* @majuss homeassistant/components/elv/* @majuss
homeassistant/components/emby/* @mezz64 homeassistant/components/emby/* @mezz64
homeassistant/components/emulated_hue/* @NobleKangaroo
homeassistant/components/enigma2/* @fbradyirl homeassistant/components/enigma2/* @fbradyirl
homeassistant/components/enocean/* @bdurrer homeassistant/components/enocean/* @bdurrer
homeassistant/components/entur_public_transport/* @hfurubotten
homeassistant/components/environment_canada/* @michaeldavie homeassistant/components/environment_canada/* @michaeldavie
homeassistant/components/ephember/* @ttroy50 homeassistant/components/ephember/* @ttroy50
homeassistant/components/epsonworkforce/* @ThaStealth homeassistant/components/epsonworkforce/* @ThaStealth
@@ -95,11 +99,13 @@ homeassistant/components/eq3btsmart/* @rytilahti
homeassistant/components/esphome/* @OttoWinter homeassistant/components/esphome/* @OttoWinter
homeassistant/components/essent/* @TheLastProject homeassistant/components/essent/* @TheLastProject
homeassistant/components/evohome/* @zxdavb homeassistant/components/evohome/* @zxdavb
homeassistant/components/fastdotcom/* @rohankapoorcom
homeassistant/components/file/* @fabaff homeassistant/components/file/* @fabaff
homeassistant/components/filter/* @dgomes homeassistant/components/filter/* @dgomes
homeassistant/components/fitbit/* @robbiet480 homeassistant/components/fitbit/* @robbiet480
homeassistant/components/fixer/* @fabaff homeassistant/components/fixer/* @fabaff
homeassistant/components/flock/* @fabaff homeassistant/components/flock/* @fabaff
homeassistant/components/flume/* @ChrisMandich
homeassistant/components/flunearyou/* @bachya homeassistant/components/flunearyou/* @bachya
homeassistant/components/fortigate/* @kifeo homeassistant/components/fortigate/* @kifeo
homeassistant/components/fortios/* @kimfrellsen homeassistant/components/fortios/* @kimfrellsen
@@ -112,6 +118,8 @@ homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geo_rss_events/* @exxamalte homeassistant/components/geo_rss_events/* @exxamalte
homeassistant/components/geonetnz_quakes/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/geonetnz_volcano/* @exxamalte
homeassistant/components/gios/* @bieniu
homeassistant/components/gitter/* @fabaff homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/gntp/* @robbiet480 homeassistant/components/gntp/* @robbiet480
@@ -125,6 +133,7 @@ homeassistant/components/growatt_server/* @indykoning
homeassistant/components/gtfs/* @robbiet480 homeassistant/components/gtfs/* @robbiet480
homeassistant/components/harmony/* @ehendrix23 homeassistant/components/harmony/* @ehendrix23
homeassistant/components/hassio/* @home-assistant/hass-io homeassistant/components/hassio/* @home-assistant/hass-io
homeassistant/components/heatmiser/* @andylockran
homeassistant/components/heos/* @andrewsayre homeassistant/components/heos/* @andrewsayre
homeassistant/components/here_travel_time/* @eifinger homeassistant/components/here_travel_time/* @eifinger
homeassistant/components/hikvision/* @mezz64 homeassistant/components/hikvision/* @mezz64
@@ -144,6 +153,7 @@ homeassistant/components/huawei_lte/* @scop
homeassistant/components/huawei_router/* @abmantis homeassistant/components/huawei_router/* @abmantis
homeassistant/components/hue/* @balloob homeassistant/components/hue/* @balloob
homeassistant/components/iaqualink/* @flz homeassistant/components/iaqualink/* @flz
homeassistant/components/icloud/* @Quentame
homeassistant/components/ign_sismologia/* @exxamalte homeassistant/components/ign_sismologia/* @exxamalte
homeassistant/components/incomfort/* @zxdavb homeassistant/components/incomfort/* @zxdavb
homeassistant/components/influxdb/* @fabaff homeassistant/components/influxdb/* @fabaff
@@ -153,7 +163,10 @@ homeassistant/components/input_number/* @home-assistant/core
homeassistant/components/input_select/* @home-assistant/core homeassistant/components/input_select/* @home-assistant/core
homeassistant/components/input_text/* @home-assistant/core homeassistant/components/input_text/* @home-assistant/core
homeassistant/components/integration/* @dgomes homeassistant/components/integration/* @dgomes
homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo
homeassistant/components/ios/* @robbiet480 homeassistant/components/ios/* @robbiet480
homeassistant/components/iperf3/* @rohankapoorcom
homeassistant/components/ipma/* @dgomes homeassistant/components/ipma/* @dgomes
homeassistant/components/iqvia/* @bachya homeassistant/components/iqvia/* @bachya
homeassistant/components/irish_rail_transport/* @ttroy50 homeassistant/components/irish_rail_transport/* @ttroy50
@@ -174,11 +187,13 @@ homeassistant/components/life360/* @pnbruckner
homeassistant/components/linky/* @Quentame homeassistant/components/linky/* @Quentame
homeassistant/components/linux_battery/* @fabaff homeassistant/components/linux_battery/* @fabaff
homeassistant/components/liveboxplaytv/* @pschmitt homeassistant/components/liveboxplaytv/* @pschmitt
homeassistant/components/local_ip/* @issacg
homeassistant/components/logger/* @home-assistant/core homeassistant/components/logger/* @home-assistant/core
homeassistant/components/logi_circle/* @evanjd homeassistant/components/logi_circle/* @evanjd
homeassistant/components/lovelace/* @home-assistant/frontend homeassistant/components/lovelace/* @home-assistant/frontend
homeassistant/components/luci/* @fbradyirl @mzdrale homeassistant/components/luci/* @fbradyirl @mzdrale
homeassistant/components/luftdaten/* @fabaff homeassistant/components/luftdaten/* @fabaff
homeassistant/components/lupusec/* @majuss
homeassistant/components/lutron/* @JonGilmore homeassistant/components/lutron/* @JonGilmore
homeassistant/components/mastodon/* @fabaff homeassistant/components/mastodon/* @fabaff
homeassistant/components/matrix/* @tinloaf homeassistant/components/matrix/* @tinloaf
@@ -193,6 +208,7 @@ homeassistant/components/mill/* @danielhiversen
homeassistant/components/min_max/* @fabaff homeassistant/components/min_max/* @fabaff
homeassistant/components/minio/* @tkislan homeassistant/components/minio/* @tkislan
homeassistant/components/mobile_app/* @robbiet480 homeassistant/components/mobile_app/* @robbiet480
homeassistant/components/modbus/* @adamchengtkc
homeassistant/components/monoprice/* @etsinko homeassistant/components/monoprice/* @etsinko
homeassistant/components/moon/* @fabaff homeassistant/components/moon/* @fabaff
homeassistant/components/mpd/* @fabaff homeassistant/components/mpd/* @fabaff
@@ -206,6 +222,7 @@ homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @awarecan homeassistant/components/nest/* @awarecan
homeassistant/components/netdata/* @fabaff homeassistant/components/netdata/* @fabaff
homeassistant/components/nextbus/* @vividboarder homeassistant/components/nextbus/* @vividboarder
homeassistant/components/nilu/* @hfurubotten
homeassistant/components/nissan_leaf/* @filcole homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmbs/* @thibmaek homeassistant/components/nmbs/* @thibmaek
homeassistant/components/no_ip/* @fabaff homeassistant/components/no_ip/* @fabaff
@@ -220,6 +237,7 @@ homeassistant/components/obihai/* @dshokouhi
homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/ombi/* @larssont homeassistant/components/ombi/* @larssont
homeassistant/components/onboarding/* @home-assistant/core homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/onewire/* @garbled1
homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/opentherm_gw/* @mvn23
homeassistant/components/openuv/* @bachya homeassistant/components/openuv/* @bachya
homeassistant/components/openweathermap/* @fabaff homeassistant/components/openweathermap/* @fabaff
@@ -237,6 +255,7 @@ homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/plex/* @jjlawren homeassistant/components/plex/* @jjlawren
homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew
homeassistant/components/point/* @fredrike homeassistant/components/point/* @fredrike
homeassistant/components/proxmoxve/* @k4ds3
homeassistant/components/ps4/* @ktnrg45 homeassistant/components/ps4/* @ktnrg45
homeassistant/components/ptvsd/* @swamp-ig homeassistant/components/ptvsd/* @swamp-ig
homeassistant/components/push/* @dgomes homeassistant/components/push/* @dgomes
@@ -266,6 +285,7 @@ homeassistant/components/seventeentrack/* @bachya
homeassistant/components/shell_command/* @home-assistant/core homeassistant/components/shell_command/* @home-assistant/core
homeassistant/components/shiftr/* @fabaff homeassistant/components/shiftr/* @fabaff
homeassistant/components/shodan/* @fabaff homeassistant/components/shodan/* @fabaff
homeassistant/components/signal_messenger/* @bbernhard
homeassistant/components/simplisafe/* @bachya homeassistant/components/simplisafe/* @bachya
homeassistant/components/sinch/* @bendikrb homeassistant/components/sinch/* @bendikrb
homeassistant/components/slide/* @ualex73 homeassistant/components/slide/* @ualex73
@@ -281,8 +301,10 @@ homeassistant/components/soma/* @ratsept
homeassistant/components/somfy/* @tetienne homeassistant/components/somfy/* @tetienne
homeassistant/components/songpal/* @rytilahti homeassistant/components/songpal/* @rytilahti
homeassistant/components/spaceapi/* @fabaff homeassistant/components/spaceapi/* @fabaff
homeassistant/components/speedtestdotnet/* @rohankapoorcom
homeassistant/components/spider/* @peternijssen homeassistant/components/spider/* @peternijssen
homeassistant/components/sql/* @dgomes homeassistant/components/sql/* @dgomes
homeassistant/components/starline/* @anonym-tsk
homeassistant/components/statistics/* @fabaff homeassistant/components/statistics/* @fabaff
homeassistant/components/stiebel_eltron/* @fucm homeassistant/components/stiebel_eltron/* @fucm
homeassistant/components/stream/* @hunterjm homeassistant/components/stream/* @hunterjm
@@ -298,11 +320,12 @@ homeassistant/components/switchmate/* @danielhiversen
homeassistant/components/syncthru/* @nielstron homeassistant/components/syncthru/* @nielstron
homeassistant/components/synology_srm/* @aerialls homeassistant/components/synology_srm/* @aerialls
homeassistant/components/syslog/* @fabaff homeassistant/components/syslog/* @fabaff
homeassistant/components/tado/* @michaelarnauts
homeassistant/components/tahoma/* @philklei homeassistant/components/tahoma/* @philklei
homeassistant/components/tautulli/* @ludeeus homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike homeassistant/components/tellduslive/* @fredrike
homeassistant/components/template/* @PhracturedBlue homeassistant/components/template/* @PhracturedBlue
homeassistant/components/tesla/* @zabuldon homeassistant/components/tesla/* @zabuldon @alandtse
homeassistant/components/tfiac/* @fredrike @mellado homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff homeassistant/components/thethingsnetwork/* @fabaff
homeassistant/components/threshold/* @fabaff homeassistant/components/threshold/* @fabaff
@@ -331,6 +354,7 @@ homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @cereal2nd homeassistant/components/velbus/* @cereal2nd
homeassistant/components/velux/* @Julius2342 homeassistant/components/velux/* @Julius2342
homeassistant/components/versasense/* @flamm3blemuff1n
homeassistant/components/version/* @fabaff homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe homeassistant/components/vesync/* @markperdue @webdjoe
homeassistant/components/vicare/* @oischinger homeassistant/components/vicare/* @oischinger
@@ -345,6 +369,7 @@ homeassistant/components/websocket_api/* @home-assistant/core
homeassistant/components/wemo/* @sqldiablo homeassistant/components/wemo/* @sqldiablo
homeassistant/components/withings/* @vangorra homeassistant/components/withings/* @vangorra
homeassistant/components/wled/* @frenck homeassistant/components/wled/* @frenck
homeassistant/components/workday/* @fabaff
homeassistant/components/worldclock/* @fabaff homeassistant/components/worldclock/* @fabaff
homeassistant/components/wwlln/* @bachya homeassistant/components/wwlln/* @bachya
homeassistant/components/xbox_live/* @MartinHjelmare homeassistant/components/xbox_live/* @MartinHjelmare

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. The process is straight-forward.
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0) - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0 and 1)
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant). - Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
- Write the code for your device, notification service, sensor, or IoT thing. - Write the code for your device, notification service, sensor, or IoT thing.
- Ensure tests work. - Ensure tests work.
@@ -12,3 +12,7 @@ The process is straight-forward.
Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details. Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details.
## Feature suggestions
If you want to suggest a new feature for Home Assistant (e.g., new integrations), please open a thread in our [Community Forum: Feature Requests](https://community.home-assistant.io/c/feature-requests).
We use [GitHub for tracking issues](https://github.com/home-assistant/home-assistant/issues), not for tracking feature requests.

View File

@@ -1,14 +1,7 @@
Home Assistant |Chat Status| Home Assistant |Chat Status|
================================================================================= =================================================================================
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control. Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts. Perfect to run on a Raspberry Pi or a local server.
To get started:
.. code:: bash
python3 -m pip install homeassistant
hass --open-ui
Check out `home-assistant.io <https://home-assistant.io>`__ for `a Check out `home-assistant.io <https://home-assistant.io>`__ for `a
demo <https://home-assistant.io/demo/>`__, `installation instructions <https://home-assistant.io/getting-started/>`__, demo <https://home-assistant.io/demo/>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,

View File

@@ -14,8 +14,6 @@ pr:
resources: resources:
containers: containers:
- container: 36
image: homeassistant/ci-azure:3.6
- container: 37 - container: 37
image: homeassistant/ci-azure:3.7 image: homeassistant/ci-azure:3.7
repositories: repositories:
@@ -25,7 +23,7 @@ resources:
endpoint: 'home-assistant' endpoint: 'home-assistant'
variables: variables:
- name: PythonMain - name: PythonMain
value: '36' value: '37'
- group: codecov - group: codecov
stages: stages:
@@ -50,6 +48,18 @@ stages:
. venv/bin/activate . venv/bin/activate
pre-commit run flake8 --all-files pre-commit run flake8 --all-files
displayName: 'Run flake8' displayName: 'Run flake8'
- script: |
. 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' - job: 'Validate'
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
@@ -87,7 +97,7 @@ stages:
pre-commit install-hooks --config .pre-commit-config-all.yaml pre-commit install-hooks --config .pre-commit-config-all.yaml
- script: | - script: |
. venv/bin/activate . venv/bin/activate
pre-commit run black --all-files pre-commit run black --all-files --show-diff-on-failure
displayName: 'Check Black formatting' displayName: 'Check Black formatting'
- stage: 'Tests' - stage: 'Tests'
@@ -100,8 +110,6 @@ stages:
strategy: strategy:
maxParallel: 3 maxParallel: 3
matrix: matrix:
Python36:
python.container: '36'
Python37: Python37:
python.container: '37' python.container: '37'
container: $[ variables['python.container'] ] container: $[ variables['python.container'] ]
@@ -158,7 +166,7 @@ stages:
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
pip install -U pip setuptools pip install -U pip setuptools wheel
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
- script: | - script: |

View File

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

View File

@@ -17,11 +17,11 @@
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# #
import sys
import os
import inspect import inspect
import os
import sys
from homeassistant.const import __version__, __short_version__ from homeassistant.const import __short_version__, __version__
PROJECT_NAME = 'Home Assistant' PROJECT_NAME = 'Home Assistant'
PROJECT_PACKAGE_NAME = 'homeassistant' PROJECT_PACKAGE_NAME = 'homeassistant'

View File

@@ -1,26 +1,23 @@
"""Start Home Assistant.""" """Start Home Assistant."""
import argparse import argparse
import asyncio
import os import os
import platform import platform
import subprocess import subprocess
import sys import sys
import threading import threading
from typing import List, Dict, Any, TYPE_CHECKING from typing import TYPE_CHECKING, Any, Dict, List
from homeassistant import monkey_patch from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
if TYPE_CHECKING: if TYPE_CHECKING:
from homeassistant import core from homeassistant import core
def set_loop() -> None: def set_loop() -> None:
"""Attempt to use uvloop.""" """Attempt to use different loop."""
import asyncio
from asyncio.events import BaseDefaultEventLoopPolicy from asyncio.events import BaseDefaultEventLoopPolicy
policy = None
if sys.platform == "win32": if sys.platform == "win32":
if hasattr(asyncio, "WindowsProactorEventLoopPolicy"): if hasattr(asyncio, "WindowsProactorEventLoopPolicy"):
# pylint: disable=no-member # pylint: disable=no-member
@@ -33,15 +30,7 @@ def set_loop() -> None:
_loop_factory = asyncio.ProactorEventLoop _loop_factory = asyncio.ProactorEventLoop
policy = ProactorPolicy() policy = ProactorPolicy()
else:
try:
import uvloop
except ImportError:
pass
else:
policy = uvloop.EventLoopPolicy()
if policy is not None:
asyncio.set_event_loop_policy(policy) asyncio.set_event_loop_policy(policy)
@@ -89,11 +78,7 @@ def ensure_config_path(config_dir: str) -> None:
try: try:
os.mkdir(lib_dir) os.mkdir(lib_dir)
except OSError: except OSError:
print( print("Fatal Error: Unable to create library directory {}".format(lib_dir))
("Fatal Error: Unable to create library " "directory {} ").format(
lib_dir
)
)
sys.exit(1) sys.exit(1)
@@ -158,7 +143,7 @@ def get_arguments() -> argparse.Namespace:
"--log-file", "--log-file",
type=str, type=str,
default=None, default=None,
help="Log file to write to. If not set, CONFIG/home-assistant.log " "is used", help="Log file to write to. If not set, CONFIG/home-assistant.log is used",
) )
parser.add_argument( parser.add_argument(
"--log-no-color", action="store_true", help="Disable color logs" "--log-no-color", action="store_true", help="Disable color logs"
@@ -272,7 +257,6 @@ def cmdline() -> List[str]:
async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int: async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
"""Set up HASS and run.""" """Set up HASS and run."""
# pylint: disable=redefined-outer-name
from homeassistant import bootstrap, core from homeassistant import bootstrap, core
hass = core.HomeAssistant() hass = core.HomeAssistant()
@@ -356,11 +340,6 @@ def main() -> int:
"""Start Home Assistant.""" """Start Home Assistant."""
validate_python() validate_python()
monkey_patch_needed = sys.version_info[:3] < (3, 6, 3)
if monkey_patch_needed and os.environ.get("HASS_NO_MONKEY") != "1":
monkey_patch.disable_c_asyncio()
monkey_patch.patch_weakref_tasks()
set_loop() set_loop()
# Run a simple daemon runner process on Windows to handle restarts # Run a simple daemon runner process on Windows to handle restarts
@@ -394,13 +373,11 @@ def main() -> int:
if args.pid_file: if args.pid_file:
write_pid(args.pid_file) write_pid(args.pid_file)
from homeassistant.util.async_ import asyncio_run exit_code = asyncio.run(setup_and_run_hass(config_dir, args))
exit_code = asyncio_run(setup_and_run_hass(config_dir, args))
if exit_code == RESTART_EXIT_CODE and not args.runner: if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart() try_to_restart()
return exit_code # type: ignore return exit_code
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,21 +1,21 @@
"""Provide an authentication layer for Home Assistant.""" """Provide an authentication layer for Home Assistant."""
import asyncio import asyncio
import logging
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import logging
from typing import Any, Dict, List, Optional, Tuple, cast from typing import Any, Dict, List, Optional, Tuple, cast
import jwt import jwt
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import callback, HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import auth_store, models from . import auth_store, models
from .const import GROUP_ID_ADMIN from .const import GROUP_ID_ADMIN
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
from .providers import auth_provider_from_config, AuthProvider, LoginFlow from .providers import AuthProvider, LoginFlow, auth_provider_from_config
EVENT_USER_ADDED = "user_added" EVENT_USER_ADDED = "user_added"
EVENT_USER_REMOVED = "user_removed" EVENT_USER_REMOVED = "user_removed"

View File

@@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import models from . import models
from .const import GROUP_ID_ADMIN, GROUP_ID_USER, GROUP_ID_READ_ONLY from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
from .permissions import PermissionLookup, system_policies from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType from .permissions.types import PolicyType

View File

@@ -7,7 +7,7 @@ from typing import Any, Dict, Optional
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from homeassistant import requirements, data_entry_flow from homeassistant import data_entry_flow, requirements
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@@ -42,7 +42,7 @@ class MultiFactorAuthModule:
self.config = config self.config = config
@property @property
def id(self) -> str: # pylint: disable=invalid-name def id(self) -> str:
"""Return id of the auth module. """Return id of the auth module.
Default is same as type Default is same as type

View File

@@ -7,9 +7,9 @@ import voluptuous as vol
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import ( from . import (
MultiFactorAuthModule,
MULTI_FACTOR_AUTH_MODULES,
MULTI_FACTOR_AUTH_MODULE_SCHEMA, MULTI_FACTOR_AUTH_MODULE_SCHEMA,
MULTI_FACTOR_AUTH_MODULES,
MultiFactorAuthModule,
SetupFlow, SetupFlow,
) )

View File

@@ -3,9 +3,9 @@
Sending HOTP through notify service Sending HOTP through notify service
""" """
import asyncio import asyncio
import logging
from collections import OrderedDict from collections import OrderedDict
from typing import Any, Dict, Optional, List import logging
from typing import Any, Dict, List, Optional
import attr import attr
import voluptuous as vol import voluptuous as vol
@@ -16,9 +16,9 @@ from homeassistant.exceptions import ServiceNotFound
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from . import ( from . import (
MultiFactorAuthModule,
MULTI_FACTOR_AUTH_MODULES,
MULTI_FACTOR_AUTH_MODULE_SCHEMA, MULTI_FACTOR_AUTH_MODULE_SCHEMA,
MULTI_FACTOR_AUTH_MODULES,
MultiFactorAuthModule,
SetupFlow, SetupFlow,
) )

View File

@@ -1,7 +1,7 @@
"""Time-based One Time Password auth module.""" """Time-based One Time Password auth module."""
import asyncio import asyncio
import logging
from io import BytesIO from io import BytesIO
import logging
from typing import Any, Dict, Optional, Tuple from typing import Any, Dict, Optional, Tuple
import voluptuous as vol import voluptuous as vol
@@ -10,9 +10,9 @@ from homeassistant.auth.models import User
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import ( from . import (
MultiFactorAuthModule,
MULTI_FACTOR_AUTH_MODULES,
MULTI_FACTOR_AUTH_MODULE_SCHEMA, MULTI_FACTOR_AUTH_MODULE_SCHEMA,
MULTI_FACTOR_AUTH_MODULES,
MultiFactorAuthModule,
SetupFlow, SetupFlow,
) )

View File

@@ -1,5 +1,6 @@
"""Auth models.""" """Auth models."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
import secrets
from typing import Dict, List, NamedTuple, Optional from typing import Dict, List, NamedTuple, Optional
import uuid import uuid
@@ -9,7 +10,6 @@ from homeassistant.util import dt as dt_util
from . import permissions as perm_mdl from . import permissions as perm_mdl
from .const import GROUP_ID_ADMIN from .const import GROUP_ID_ADMIN
from .util import generate_secret
TOKEN_TYPE_NORMAL = "normal" TOKEN_TYPE_NORMAL = "normal"
TOKEN_TYPE_SYSTEM = "system" TOKEN_TYPE_SYSTEM = "system"
@@ -96,8 +96,8 @@ class RefreshToken:
) )
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
created_at = attr.ib(type=datetime, factory=dt_util.utcnow) created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
token = attr.ib(type=str, factory=lambda: generate_secret(64)) token = attr.ib(type=str, factory=lambda: secrets.token_hex(64))
jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64)) jwt_key = attr.ib(type=str, factory=lambda: secrets.token_hex(64))
last_used_at = attr.ib(type=Optional[datetime], default=None) last_used_at = attr.ib(type=Optional[datetime], default=None)
last_used_ip = attr.ib(type=Optional[str], default=None) last_used_ip = attr.ib(type=Optional[str], default=None)

View File

@@ -5,13 +5,12 @@ from typing import Any, Callable, Optional
import voluptuous as vol import voluptuous as vol
from .const import CAT_ENTITIES from .const import CAT_ENTITIES
from .models import PermissionLookup
from .types import PolicyType
from .entities import ENTITY_POLICY_SCHEMA, compile_entities from .entities import ENTITY_POLICY_SCHEMA, compile_entities
from .merge import merge_policies # noqa: F401 from .merge import merge_policies # noqa: F401
from .models import PermissionLookup
from .types import PolicyType
from .util import test_all from .util import test_all
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -58,15 +57,12 @@ class PolicyPermissions(AbstractPermissions):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
"""Equals check.""" """Equals check."""
# pylint: disable=protected-access
return isinstance(other, PolicyPermissions) and other._policy == self._policy return isinstance(other, PolicyPermissions) and other._policy == self._policy
class _OwnerPermissions(AbstractPermissions): class _OwnerPermissions(AbstractPermissions):
"""Owner permissions.""" """Owner permissions."""
# pylint: disable=no-self-use
def access_all_entities(self, key: str) -> bool: def access_all_entities(self, key: str) -> bool:
"""Check if we have a certain access to all entities.""" """Check if we have a certain access to all entities."""
return True return True

View File

@@ -4,11 +4,10 @@ from typing import Callable, Optional
import voluptuous as vol import voluptuous as vol
from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT from .const import POLICY_CONTROL, POLICY_EDIT, POLICY_READ, SUBCAT_ALL
from .models import PermissionLookup from .models import PermissionLookup
from .types import CategoryType, SubCategoryDict, ValueType from .types import CategoryType, SubCategoryDict, ValueType
from .util import SubCatLookupType, compile_policy, lookup_all
from .util import SubCatLookupType, lookup_all, compile_policy
SINGLE_ENTITY_SCHEMA = vol.Any( SINGLE_ENTITY_SCHEMA = vol.Any(
True, True,

View File

@@ -1,7 +1,7 @@
"""Merging of policies.""" """Merging of policies."""
from typing import cast, Dict, List, Set from typing import Dict, List, Set, cast
from .types import PolicyType, CategoryType from .types import CategoryType, PolicyType
def merge_policies(policies: List[PolicyType]) -> PolicyType: def merge_policies(policies: List[PolicyType]) -> PolicyType:

View File

@@ -1,5 +1,5 @@
"""System policies.""" """System policies."""
from .const import CAT_ENTITIES, SUBCAT_ALL, POLICY_READ from .const import CAT_ENTITIES, POLICY_READ, SUBCAT_ALL
ADMIN_POLICY = {CAT_ENTITIES: True} ADMIN_POLICY = {CAT_ENTITIES: True}

View File

@@ -1,6 +1,5 @@
"""Helpers to deal with permissions.""" """Helpers to deal with permissions."""
from functools import wraps from functools import wraps
from typing import Callable, Dict, List, Optional, cast from typing import Callable, Dict, List, Optional, cast
from .const import SUBCAT_ALL from .const import SUBCAT_ALL

View File

@@ -8,8 +8,8 @@ import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from homeassistant import data_entry_flow, requirements from homeassistant import data_entry_flow, requirements
from homeassistant.core import callback, HomeAssistant
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
@@ -48,7 +48,7 @@ class AuthProvider:
self.config = config self.config = config
@property @property
def id(self) -> Optional[str]: # pylint: disable=invalid-name def id(self) -> Optional[str]:
"""Return id of the auth provider. """Return id of the auth provider.
Optional, can be None. Optional, can be None.

View File

@@ -1,20 +1,18 @@
"""Auth provider that validates credentials via an external command.""" """Auth provider that validates credentials via an external command."""
from typing import Any, Dict, Optional, cast
import asyncio.subprocess import asyncio.subprocess
import collections import collections
import logging import logging
import os import os
from typing import Any, Dict, Optional, cast
import voluptuous as vol import voluptuous as vol
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
CONF_COMMAND = "command" CONF_COMMAND = "command"
CONF_ARGS = "args" CONF_ARGS = "args"
CONF_META = "meta" CONF_META = "meta"
@@ -78,7 +76,7 @@ class CommandLineAuthProvider(AuthProvider):
if process.returncode != 0: if process.returncode != 0:
_LOGGER.error( _LOGGER.error(
"User %r failed to authenticate, command exited " "with code %d.", "User %r failed to authenticate, command exited with code %d.",
username, username,
process.returncode, process.returncode,
) )

View File

@@ -3,21 +3,18 @@ import asyncio
import base64 import base64
from collections import OrderedDict from collections import OrderedDict
import logging import logging
from typing import Any, Dict, List, Optional, Set, cast from typing import Any, Dict, List, Optional, Set, cast
import bcrypt import bcrypt
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_ID from homeassistant.const import CONF_ID
from homeassistant.core import callback, HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant" STORAGE_KEY = "auth_provider.homeassistant"

View File

@@ -5,13 +5,12 @@ from typing import Any, Dict, Optional, cast
import voluptuous as vol import voluptuous as vol
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
USER_SCHEMA = vol.Schema( USER_SCHEMA = vol.Schema(
{ {
vol.Required("username"): str, vol.Required("username"): str,

View File

@@ -12,9 +12,9 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from .. import AuthManager from .. import AuthManager
from ..models import Credentials, UserMeta, User from ..models import Credentials, User, UserMeta
AUTH_PROVIDER_TYPE = "legacy_api_password" AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password" CONF_API_PASSWORD = "api_password"

View File

@@ -3,15 +3,16 @@
It shows list of users if access from trusted network. It shows list of users if access from trusted network.
Abort login flow if not access from trusted network. Abort login flow if not access from trusted network.
""" """
from ipaddress import ip_network, IPv4Address, IPv6Address, IPv4Network, IPv6Network from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network
from typing import Any, Dict, List, Optional, Union, cast from typing import Any, Dict, List, Optional, Union, cast
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
IPAddress = Union[IPv4Address, IPv6Address] IPAddress = Union[IPv4Address, IPv6Address]

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.""" """Provide methods to bootstrap a Home Assistant instance."""
import asyncio import asyncio
from collections import OrderedDict
import logging import logging
import logging.handlers import logging.handlers
import os import os
import sys import sys
from time import time from time import time
from collections import OrderedDict from typing import Any, Dict, Optional, Set
from typing import Any, Optional, Dict, Set
import voluptuous as vol import voluptuous as vol
from homeassistant import core, config as conf_util, config_entries, loader from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.const import (
EVENT_HOMEASSISTANT_CLOSE,
REQUIRED_NEXT_PYTHON_DATE,
REQUIRED_NEXT_PYTHON_VER,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.logging import AsyncHandler from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, is_virtual_env from homeassistant.util.package import async_get_user_site, is_virtual_env
from homeassistant.util.yaml import clear_secret_cache from homeassistant.util.yaml import clear_secret_cache
from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -62,7 +66,7 @@ async def async_from_config_dict(
hass.config.skip_pip = skip_pip hass.config.skip_pip = skip_pip
if skip_pip: if skip_pip:
_LOGGER.warning( _LOGGER.warning(
"Skipping pip installation of required modules. " "This may cause issues" "Skipping pip installation of required modules. This may cause issues"
) )
core_config = config.get(core.DOMAIN, {}) core_config = config.get(core.DOMAIN, {})
@@ -95,11 +99,14 @@ async def async_from_config_dict(
stop = time() stop = time()
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start) _LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
if sys.version_info[:3] < (3, 7, 0): if REQUIRED_NEXT_PYTHON_DATE and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER:
msg = ( msg = (
"Python 3.6 support is deprecated and will " "Support for the running Python version "
"be removed in the first release after December 15, 2019. Please " f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
"upgrade Python to 3.7.0 or higher." f"be removed in the first release after {REQUIRED_NEXT_PYTHON_DATE}. "
"Please upgrade Python to "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
"higher."
) )
_LOGGER.warning(msg) _LOGGER.warning(msg)
hass.components.persistent_notification.async_create( hass.components.persistent_notification.async_create(
@@ -161,7 +168,7 @@ def async_enable_logging(
This method must be run in the event loop. This method must be run in the event loop.
""" """
fmt = "%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s" fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
datefmt = "%Y-%m-%d %H:%M:%S" datefmt = "%Y-%m-%d %H:%M:%S"
if not log_no_color: if not log_no_color:

View File

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

View File

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

View File

@@ -5,8 +5,8 @@
}, },
"error": { "error": {
"connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a Abode.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a Abode.",
"identifier_exists": "\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430.", "identifier_exists": "\u0423\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430.",
"invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435."
}, },
"step": { "step": {
"user": { "user": {

View File

@@ -20,14 +20,14 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from .const import ( from .const import (
ATTRIBUTION, ATTRIBUTION,
DOMAIN,
DEFAULT_CACHEDB, DEFAULT_CACHEDB,
DOMAIN,
SIGNAL_CAPTURE_IMAGE, SIGNAL_CAPTURE_IMAGE,
SIGNAL_TRIGGER_QUICK_ACTION, SIGNAL_TRIGGER_QUICK_ACTION,
) )

View File

@@ -2,6 +2,10 @@
import logging import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
@@ -51,6 +55,11 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
state = None state = None
return state return state
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
self._device.set_standby() self._device.set_standby()

View File

@@ -10,7 +10,7 @@ from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import callback
from .const import DOMAIN, DEFAULT_CACHEDB # pylint: disable=W0611 from .const import DEFAULT_CACHEDB, DOMAIN # pylint: disable=unused-import
CONF_POLLING = "polling" CONF_POLLING = "polling"

View File

@@ -72,19 +72,19 @@ class AbodeSensor(AbodeDevice):
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self._sensor_type == "temp": if self._sensor_type == CONST.TEMP_STATUS_KEY:
return self._device.temp return self._device.temp
if self._sensor_type == "humidity": if self._sensor_type == CONST.HUMI_STATUS_KEY:
return self._device.humidity return self._device.humidity
if self._sensor_type == "lux": if self._sensor_type == CONST.LUX_STATUS_KEY:
return self._device.lux return self._device.lux
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the units of measurement.""" """Return the units of measurement."""
if self._sensor_type == "temp": if self._sensor_type == CONST.TEMP_STATUS_KEY:
return self._device.temp_unit return self._device.temp_unit
if self._sensor_type == "humidity": if self._sensor_type == CONST.HUMI_STATUS_KEY:
return self._device.humidity_unit return self._device.humidity_unit
if self._sensor_type == "lux": if self._sensor_type == CONST.LUX_STATUS_KEY:
return self._device.lux_unit return self._device.lux_unit

View File

@@ -1,17 +1,17 @@
"""Use serial protocol of Acer projector to obtain state of the projector.""" """Use serial protocol of Acer projector to obtain state of the projector."""
import logging import logging
import re import re
import serial
import serial
import voluptuous as vol import voluptuous as vol
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
from homeassistant.const import ( from homeassistant.const import (
STATE_ON,
STATE_OFF,
STATE_UNKNOWN,
CONF_NAME,
CONF_FILENAME, CONF_FILENAME,
CONF_NAME,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv

View File

@@ -1,18 +1,19 @@
"""Support for Actiontec MI424WR (Verizon FIOS) routers.""" """Support for Actiontec MI424WR (Verizon FIOS) routers."""
from collections import namedtuple
import logging import logging
import re import re
import telnetlib import telnetlib
from collections import namedtuple
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, DOMAIN,
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
DeviceScanner, DeviceScanner,
) )
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -1,16 +1,18 @@
{ {
"config": { "config": {
"abort": { "abort": {
"adguard_home_addon_outdated": "Denne integration kr\u00e6ver AdGuard Home {minimal_version} eller h\u00f8jere, du har {current_version}. Opdater venligst din Hass.io AdGuard Home-tilf\u00f8jelse.",
"adguard_home_outdated": "Denne integration kr\u00e6ver AdGuard Home {minimal_version} eller h\u00f8jere, du har {current_version}.",
"existing_instance_updated": "Opdaterede eksisterende konfiguration.", "existing_instance_updated": "Opdaterede eksisterende konfiguration.",
"single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af AdGuard Home." "single_instance_allowed": "Kun en enkelt konfiguration af AdGuard Home er tilladt."
}, },
"error": { "error": {
"connection_error": "Forbindelse mislykkedes." "connection_error": "Forbindelse mislykkedes."
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
"description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home, der leveres af Hass.io add-on: {addon}?", "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Hass.io-tilf\u00f8jelsen: {addon}?",
"title": "AdGuard Home via Hass.io add-on" "title": "AdGuard Home via Hass.io-tilf\u00f8jelse"
}, },
"user": { "user": {
"data": { "data": {
@@ -21,8 +23,8 @@
"username": "Brugernavn", "username": "Brugernavn",
"verify_ssl": "AdGuard Home bruger et korrekt certifikat" "verify_ssl": "AdGuard Home bruger et korrekt certifikat"
}, },
"description": "Konfigurer din AdGuard Home instans for at tillade overv\u00e5gning og kontrol.", "description": "Konfigurer din AdGuard Home-instans for at tillade overv\u00e5gning og kontrol.",
"title": "Link AdGuard Home." "title": "Forbind din AdGuard Home."
} }
}, },
"title": "AdGuard Home" "title": "AdGuard Home"

View File

@@ -1,7 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo Hass.io AdGuard Home.", "adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo AdGuard Home di Hass.io.",
"adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.", "adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.",
"existing_instance_updated": "Configurazione esistente aggiornata.", "existing_instance_updated": "Configurazione esistente aggiornata.",
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home." "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."

View File

@@ -178,7 +178,7 @@ class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
"""Initialize AdGuard Home sensor.""" """Initialize AdGuard Home sensor."""
super().__init__( super().__init__(
adguard, adguard,
"Searches Safe Search Enforced", "AdGuard Safe Searches Enforced",
"mdi:shield-search", "mdi:shield-search",
"enforced_safesearch", "enforced_safesearch",
"requests", "requests",

View File

@@ -1,14 +1,13 @@
"""Support for Automation Device Specification (ADS).""" """Support for Automation Device Specification (ADS)."""
import threading
import struct
import logging
import ctypes
from collections import namedtuple
import asyncio import asyncio
from collections import namedtuple
import ctypes
import logging
import struct
import threading
import async_timeout import async_timeout
import pyads import pyads
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (

View File

@@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -4,25 +4,25 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.cover import ( from homeassistant.components.cover import (
PLATFORM_SCHEMA,
SUPPORT_OPEN,
SUPPORT_CLOSE,
SUPPORT_STOP,
SUPPORT_SET_POSITION,
ATTR_POSITION, ATTR_POSITION,
DEVICE_CLASSES_SCHEMA, DEVICE_CLASSES_SCHEMA,
PLATFORM_SCHEMA,
SUPPORT_CLOSE,
SUPPORT_OPEN,
SUPPORT_SET_POSITION,
SUPPORT_STOP,
CoverDevice, CoverDevice,
) )
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import ( from . import (
CONF_ADS_VAR, CONF_ADS_VAR,
CONF_ADS_VAR_POSITION, CONF_ADS_VAR_POSITION,
DATA_ADS, DATA_ADS,
AdsEntity,
STATE_KEY_STATE,
STATE_KEY_POSITION, STATE_KEY_POSITION,
STATE_KEY_STATE,
AdsEntity,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -16,9 +16,9 @@ from . import (
CONF_ADS_VAR, CONF_ADS_VAR,
CONF_ADS_VAR_BRIGHTNESS, CONF_ADS_VAR_BRIGHTNESS,
DATA_ADS, DATA_ADS,
AdsEntity,
STATE_KEY_BRIGHTNESS, STATE_KEY_BRIGHTNESS,
STATE_KEY_STATE, STATE_KEY_STATE,
AdsEntity,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -8,7 +8,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, AdsEntity, STATE_KEY_STATE from . import CONF_ADS_FACTOR, CONF_ADS_TYPE, CONF_ADS_VAR, STATE_KEY_STATE, AdsEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -3,11 +3,11 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import CONF_ADS_VAR, DATA_ADS, AdsEntity, STATE_KEY_STATE from . import CONF_ADS_VAR, DATA_ADS, STATE_KEY_STATE, AdsEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -2,6 +2,7 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
from pyaftership.tracker import Tracking
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
@@ -11,6 +12,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -56,8 +58,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the AfterShip sensor platform.""" """Set up the AfterShip sensor platform."""
from pyaftership.tracker import Tracking
apikey = config[CONF_API_KEY] apikey = config[CONF_API_KEY]
name = config[CONF_NAME] name = config[CONF_NAME]

View File

@@ -2,12 +2,12 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_BASE,
) )
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -3,7 +3,7 @@
"error": { "error": {
"auth": "API-n\u00f8glen er ikke korrekt.", "auth": "API-n\u00f8glen er ikke korrekt.",
"name_exists": "Navnet findes allerede.", "name_exists": "Navnet findes allerede.",
"wrong_location": "Ingen Airly m\u00e5lestationer i dette omr\u00e5de." "wrong_location": "Ingen Airly-m\u00e5lestationer i dette omr\u00e5de."
}, },
"step": { "step": {
"user": { "user": {
@@ -13,7 +13,7 @@
"longitude": "L\u00e6ngdegrad", "longitude": "L\u00e6ngdegrad",
"name": "Integrationens navn" "name": "Integrationens navn"
}, },
"description": "Konfigurer Airly luftkvalitet integration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register", "description": "Konfigurer Airly luftkvalitetsintegration. For at generere API-n\u00f8gle, g\u00e5 til https://developer.airly.eu/register",
"title": "Airly" "title": "Airly"
} }
}, },

View File

@@ -10,7 +10,6 @@ import async_timeout
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import Config, HomeAssistant from homeassistant.core import Config, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import Throttle from homeassistant.util import Throttle
@@ -48,9 +47,6 @@ async def async_setup_entry(hass, config_entry):
await airly.async_update() await airly.async_update()
if not airly.data:
raise ConfigEntryNotReady()
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly
hass.async_create_task( hass.async_create_task(

View File

@@ -1,11 +1,11 @@
"""Support for the Airly air_quality service.""" """Support for the Airly air_quality service."""
from homeassistant.components.air_quality import ( from homeassistant.components.air_quality import (
AirQualityEntity,
ATTR_AQI, ATTR_AQI,
ATTR_PM_10,
ATTR_PM_2_5, ATTR_PM_2_5,
ATTR_PM_10,
AirQualityEntity,
) )
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from .const import ( from .const import (
ATTR_API_ADVICE, ATTR_API_ADVICE,
@@ -35,10 +35,13 @@ LABEL_PM_10_PERCENT = f"{ATTR_PM_10}_percent_of_limit"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Airly air_quality entity based on a config entry.""" """Set up Airly air_quality entity based on a config entry."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
unique_id = f"{latitude}-{longitude}"
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
async_add_entities([AirlyAirQuality(data, name)], True) async_add_entities([AirlyAirQuality(data, name, unique_id)], True)
def round_state(func): def round_state(func):
@@ -56,11 +59,12 @@ def round_state(func):
class AirlyAirQuality(AirQualityEntity): class AirlyAirQuality(AirQualityEntity):
"""Define an Airly air quality.""" """Define an Airly air quality."""
def __init__(self, airly, name): def __init__(self, airly, name, unique_id):
"""Initialize.""" """Initialize."""
self.airly = airly self.airly = airly
self.data = airly.data self.data = airly.data
self._name = name self._name = name
self._unique_id = unique_id
self._pm_2_5 = None self._pm_2_5 = None
self._pm_10 = None self._pm_10 = None
self._aqi = None self._aqi = None
@@ -108,12 +112,12 @@ class AirlyAirQuality(AirQualityEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique_id for this entity.""" """Return a unique_id for this entity."""
return f"{self.airly.latitude}-{self.airly.longitude}" return self._unique_id
@property @property
def available(self): def available(self):
"""Return True if entity is available.""" """Return True if entity is available."""
return bool(self.airly.data) return bool(self.data)
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@@ -132,7 +136,6 @@ class AirlyAirQuality(AirQualityEntity):
if self.airly.data: if self.airly.data:
self.data = self.airly.data self.data = self.airly.data
self._pm_10 = self.data[ATTR_API_PM10] self._pm_10 = self.data[ATTR_API_PM10]
self._pm_2_5 = self.data[ATTR_API_PM25] self._pm_2_5 = self.data[ATTR_API_PM25]
self._aqi = self.data[ATTR_API_CAQI] self._aqi = self.data[ATTR_API_CAQI]

View File

@@ -1,14 +1,14 @@
"""Adds config flow for Airly.""" """Adds config flow for Airly."""
import async_timeout
import voluptuous as vol
from airly import Airly from airly import Airly
from airly.exceptions import AirlyError from airly.exceptions import AirlyError
import async_timeout
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS

View File

@@ -2,6 +2,8 @@
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME, CONF_NAME,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
@@ -60,12 +62,16 @@ SENSOR_TYPES = {
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Airly sensor entities based on a config entry.""" """Set up Airly sensor entities based on a config entry."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
sensors = [] sensors = []
for sensor in SENSOR_TYPES: for sensor in SENSOR_TYPES:
sensors.append(AirlySensor(data, name, sensor)) unique_id = f"{latitude}-{longitude}-{sensor.lower()}"
sensors.append(AirlySensor(data, name, sensor, unique_id))
async_add_entities(sensors, True) async_add_entities(sensors, True)
@@ -84,11 +90,12 @@ def round_state(func):
class AirlySensor(Entity): class AirlySensor(Entity):
"""Define an Airly sensor.""" """Define an Airly sensor."""
def __init__(self, airly, name, kind): def __init__(self, airly, name, kind, unique_id):
"""Initialize.""" """Initialize."""
self.airly = airly self.airly = airly
self.data = airly.data self.data = airly.data
self._name = name self._name = name
self._unique_id = unique_id
self.kind = kind self.kind = kind
self._device_class = None self._device_class = None
self._state = None self._state = None
@@ -130,7 +137,7 @@ class AirlySensor(Entity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique_id for this entity.""" """Return a unique_id for this entity."""
return f"{self.airly.latitude}-{self.airly.longitude}-{self.kind.lower()}" return self._unique_id
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
@@ -140,7 +147,7 @@ class AirlySensor(Entity):
@property @property
def available(self): def available(self):
"""Return True if entity is available.""" """Return True if entity is available."""
return bool(self.airly.data) return bool(self.data)
async def async_update(self): async def async_update(self):
"""Update the sensor.""" """Update the sensor."""

View File

@@ -6,6 +6,13 @@
"arm_night": "\u0421\u043b\u043e\u0436\u0438 {entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 \u0432 \u043d\u043e\u0449\u0435\u043d \u0440\u0435\u0436\u0438\u043c", "arm_night": "\u0421\u043b\u043e\u0436\u0438 {entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 \u0432 \u043d\u043e\u0449\u0435\u043d \u0440\u0435\u0436\u0438\u043c",
"disarm": "\u0414\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0439 {entity_name}", "disarm": "\u0414\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0439 {entity_name}",
"trigger": "\u0417\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u0430\u043d\u0435 {entity_name}" "trigger": "\u0417\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u0430\u043d\u0435 {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430",
"armed_home": "{entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 - \u0432\u043a\u044a\u0449\u0438",
"armed_night": "{entity_name} \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430 - \u043d\u043e\u0449",
"disarmed": "{entity_name} \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0430",
"triggered": "{entity_name} \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u0430\u043d\u0430"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Activa {entity_name} nocturn", "arm_night": "Activa {entity_name} nocturn",
"disarm": "Desactiva {entity_name}", "disarm": "Desactiva {entity_name}",
"trigger": "Dispara {entity_name}" "trigger": "Dispara {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} activada en mode a fora",
"armed_home": "{entity_name} activada en mode a casa",
"armed_night": "{entity_name} activada en mode nocturn",
"disarmed": "{entity_name} desactivada",
"triggered": "{entity_name} disparat/ada"
} }
} }
} }

View File

@@ -1,7 +1,18 @@
{ {
"device_automation": { "device_automation": {
"action_type": { "action_type": {
"arm_away": "Tilkobl {entity_name} ude",
"arm_home": "Tilkobl {entity_name} hjemme",
"arm_night": "Tilkobl {entity_name} nat",
"disarm": "Frakobl {entity_name}",
"trigger": "Udl\u00f8s {entity_name}" "trigger": "Udl\u00f8s {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} tilkoblet ude",
"armed_home": "{entity_name} tilkoblet hjemme",
"armed_night": "{entity_name} tilkoblet nat",
"disarmed": "{entity_name} frakoblet",
"triggered": "{entity_name} udl\u00f8st"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Arm {entity_name} night", "arm_night": "Arm {entity_name} night",
"disarm": "Disarm {entity_name}", "disarm": "Disarm {entity_name}",
"trigger": "Trigger {entity_name}" "trigger": "Trigger {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} armed away",
"armed_home": "{entity_name} armed home",
"armed_night": "{entity_name} armed night",
"disarmed": "{entity_name} disarmed",
"triggered": "{entity_name} triggered"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Armar {entity_name} por la noche", "arm_night": "Armar {entity_name} por la noche",
"disarm": "Desarmar {entity_name}", "disarm": "Desarmar {entity_name}",
"trigger": "Lanzar {entity_name}" "trigger": "Lanzar {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} armado fuera",
"armed_home": "{entity_name} armado en casa",
"armed_night": "{entity_name} armado modo noche",
"disarmed": "{entity_name} desarmado",
"triggered": "{entity_name} activado"
} }
} }
} }

View File

@@ -1,11 +1,18 @@
{ {
"device_automation": { "device_automation": {
"action_type": { "action_type": {
"arm_away": "Armer {entity_name} mode sortie", "arm_away": "Armer {entity_name} en mode \"sortie\"",
"arm_home": "Armer {entity_name} mode \u00e0 la maison", "arm_home": "Armer {entity_name} en mode \"maison\"",
"arm_night": "Armer {entity_name} mode nuit", "arm_night": "Armer {entity_name} en mode \"nuit\"",
"disarm": "D\u00e9sarmer {entity_name}", "disarm": "D\u00e9sarmer {entity_name}",
"trigger": "D\u00e9clencheur {entity_name}" "trigger": "D\u00e9clencheur {entity_name}"
},
"trigger_type": {
"armed_away": "Armer {entity_name} en mode \"sortie\"",
"armed_home": "Armer {entity_name} en mode \"maison\"",
"armed_night": "Armer {entity_name} en mode \"nuit\"",
"disarmed": "{entity_name} d\u00e9sarm\u00e9",
"triggered": "{entity_name} d\u00e9clench\u00e9"
} }
} }
} }

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": "Armare {entity_name} notte", "arm_night": "Armare {entity_name} notte",
"disarm": "Disarmare {entity_name}", "disarm": "Disarmare {entity_name}",
"trigger": "Attivazione {entity_name}" "trigger": "Attivazione {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} armata modalit\u00e0 fuori casa",
"armed_home": "{entity_name} armata modalit\u00e0 a casa",
"armed_night": "{entity_name} armata modalit\u00e0 notte",
"disarmed": "{entity_name} disarmato",
"triggered": "{entity_name} attivato"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44", "arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44",
"disarm": "{entity_name} \uacbd\ube44\ud574\uc81c", "disarm": "{entity_name} \uacbd\ube44\ud574\uc81c",
"trigger": "{entity_name} \ud2b8\ub9ac\uac70" "trigger": "{entity_name} \ud2b8\ub9ac\uac70"
},
"trigger_type": {
"armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c",
"armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c",
"armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c",
"disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c\ub420 \ub54c",
"triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub420 \ub54c"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "{entity_name} fir Nuecht uschalten", "arm_night": "{entity_name} fir Nuecht uschalten",
"disarm": "{entity_name} entsch\u00e4rfen", "disarm": "{entity_name} entsch\u00e4rfen",
"trigger": "{entity_name} ausl\u00e9isen" "trigger": "{entity_name} ausl\u00e9isen"
},
"trigger_type": {
"armed_away": "{entity_name} ugeschalt fir Ennerwee",
"armed_home": "{entity_name} ugeschalt fir Doheem",
"armed_night": "{entity_name} ugeschalt fir Nuecht",
"disarmed": "{entity_name} entsch\u00e4rft",
"triggered": "{entity_name} ausgel\u00e9ist"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Inschakelen {entity_name} nacht", "arm_night": "Inschakelen {entity_name} nacht",
"disarm": "Uitschakelen {entity_name}", "disarm": "Uitschakelen {entity_name}",
"trigger": "Trigger {entity_name}" "trigger": "Trigger {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} afwezig ingeschakeld",
"armed_home": "{entity_name} thuis ingeschakeld",
"armed_night": "{entity_name} nachtstand ingeschakeld",
"disarmed": "{entity_name} uitgeschakeld",
"triggered": "{entity_name} geactiveerd"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Aktiver {entity_name} natt", "arm_night": "Aktiver {entity_name} natt",
"disarm": "Deaktiver {entity_name}", "disarm": "Deaktiver {entity_name}",
"trigger": "Utl\u00f8ser {entity_name}" "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",
"disarmed": "{entity_name} deaktivert",
"triggered": "{entity_name} utl\u00f8st"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "uzbr\u00f3j (noc) {entity_name}", "arm_night": "uzbr\u00f3j (noc) {entity_name}",
"disarm": "rozbr\u00f3j {entity_name}", "disarm": "rozbr\u00f3j {entity_name}",
"trigger": "wyzw\u00f3l {entity_name}" "trigger": "wyzw\u00f3l {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} zostanie uzbrojony (poza domem)",
"armed_home": "{entity_name} zostanie uzbrojony (w domu)",
"armed_night": "{entity_name} zostanie uzbrojony (noc)",
"disarmed": "{entity_name} zostanie rozbrojony",
"triggered": "{entity_name} zostanie wyzwolony"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Armar {entity_name} noite", "arm_night": "Armar {entity_name} noite",
"disarm": "Desarmar {entity_name}", "disarm": "Desarmar {entity_name}",
"trigger": "Disparar {entidade_nome}" "trigger": "Disparar {entidade_nome}"
},
"trigger_type": {
"armed_away": "{entity_name} armado modo longe",
"armed_home": "{entidade_nome} armadado modo casa ",
"armed_night": "{entity_name} armadado para noite",
"disarmed": "{entity_name} desarmado",
"triggered": "{entity_name} acionado"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u041d\u043e\u0447\u044c\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}", "arm_night": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u041d\u043e\u0447\u044c\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
"disarm": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0445\u0440\u0430\u043d\u0443 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}", "disarm": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0445\u0440\u0430\u043d\u0443 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
"trigger": "{entity_name} \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442" "trigger": "{entity_name} \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442"
},
"trigger_type": {
"armed_away": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u041d\u0435 \u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
"armed_home": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u0414\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
"armed_night": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u0440\u0430\u043d\u044b \"\u041d\u043e\u0447\u044c\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
"disarmed": "\u041e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043e\u0445\u0440\u0430\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 {entity_name}",
"triggered": "{entity_name} \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "Vklju\u010di {entity_name} no\u010d", "arm_night": "Vklju\u010di {entity_name} no\u010d",
"disarm": "Razoro\u017ei {entity_name}", "disarm": "Razoro\u017ei {entity_name}",
"trigger": "Spro\u017ei {entity_name}" "trigger": "Spro\u017ei {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} oboro\u017een - zdoma",
"armed_home": "{entity_name} oboro\u017een - dom",
"armed_night": "{entity_name} oboro\u017een - no\u010d",
"disarmed": "{entity_name} razoro\u017een",
"triggered": "{entity_name} spro\u017een"
} }
} }
} }

View File

@@ -6,6 +6,13 @@
"arm_night": "\u8a2d\u5b9a {entity_name} \u591c\u9593\u6a21\u5f0f", "arm_night": "\u8a2d\u5b9a {entity_name} \u591c\u9593\u6a21\u5f0f",
"disarm": "\u89e3\u9664 {entity_name}", "disarm": "\u89e3\u9664 {entity_name}",
"trigger": "\u89f8\u767c {entity_name}" "trigger": "\u89f8\u767c {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} \u8a2d\u5b9a\u5916\u51fa",
"armed_home": "{entity_name} \u8a2d\u5b9a\u5728\u5bb6",
"armed_night": "{entity_name} \u8a2d\u5b9a\u591c\u9593",
"disarmed": "{entity_name} \u5df2\u89e3\u9664",
"triggered": "{entity_name} \u5df2\u89f8\u767c"
} }
} }
} }

View File

@@ -1,4 +1,5 @@
"""Component to interface with an alarm control panel.""" """Component to interface with an alarm control panel."""
from abc import abstractmethod
from datetime import timedelta from datetime import timedelta
import logging import logging
@@ -7,22 +8,30 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
ATTR_CODE_FORMAT, ATTR_CODE_FORMAT,
SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_ARM_CUSTOM_BYPASS, SERVICE_ALARM_ARM_CUSTOM_BYPASS,
) SERVICE_ALARM_ARM_HOME,
from homeassistant.helpers.config_validation import ( # noqa: F401 SERVICE_ALARM_ARM_NIGHT,
ENTITY_SERVICE_SCHEMA, SERVICE_ALARM_DISARM,
PLATFORM_SCHEMA, SERVICE_ALARM_TRIGGER,
PLATFORM_SCHEMA_BASE,
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
make_entity_service_schema,
)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from .const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_TRIGGER,
)
DOMAIN = "alarm_control_panel" DOMAIN = "alarm_control_panel"
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
ATTR_CHANGED_BY = "changed_by" ATTR_CHANGED_BY = "changed_by"
@@ -32,9 +41,7 @@ ATTR_CODE_ARM_REQUIRED = "code_arm_required"
ENTITY_ID_FORMAT = DOMAIN + ".{}" ENTITY_ID_FORMAT = DOMAIN + ".{}"
ALARM_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend( ALARM_SERVICE_SCHEMA = make_entity_service_schema({vol.Optional(ATTR_CODE): cv.string})
{vol.Optional(ATTR_CODE): cv.string}
)
async def async_setup(hass, config): async def async_setup(hass, config):
@@ -49,21 +56,34 @@ async def async_setup(hass, config):
SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA, "async_alarm_disarm" SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA, "async_alarm_disarm"
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_HOME, ALARM_SERVICE_SCHEMA, "async_alarm_arm_home" SERVICE_ALARM_ARM_HOME,
ALARM_SERVICE_SCHEMA,
"async_alarm_arm_home",
[SUPPORT_ALARM_ARM_HOME],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_AWAY, ALARM_SERVICE_SCHEMA, "async_alarm_arm_away" SERVICE_ALARM_ARM_AWAY,
ALARM_SERVICE_SCHEMA,
"async_alarm_arm_away",
[SUPPORT_ALARM_ARM_AWAY],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_NIGHT, ALARM_SERVICE_SCHEMA, "async_alarm_arm_night" SERVICE_ALARM_ARM_NIGHT,
ALARM_SERVICE_SCHEMA,
"async_alarm_arm_night",
[SUPPORT_ALARM_ARM_NIGHT],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_CUSTOM_BYPASS, SERVICE_ALARM_ARM_CUSTOM_BYPASS,
ALARM_SERVICE_SCHEMA, ALARM_SERVICE_SCHEMA,
"async_alarm_arm_custom_bypass", "async_alarm_arm_custom_bypass",
[SUPPORT_ALARM_ARM_CUSTOM_BYPASS],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_TRIGGER, ALARM_SERVICE_SCHEMA, "async_alarm_trigger" SERVICE_ALARM_TRIGGER,
ALARM_SERVICE_SCHEMA,
"async_alarm_trigger",
[SUPPORT_ALARM_TRIGGER],
) )
return True return True
@@ -79,7 +99,6 @@ async def async_unload_entry(hass, entry):
return await hass.data[DOMAIN].async_unload_entry(entry) return await hass.data[DOMAIN].async_unload_entry(entry)
# pylint: disable=no-self-use
class AlarmControlPanel(Entity): class AlarmControlPanel(Entity):
"""An abstract class for alarm control devices.""" """An abstract class for alarm control devices."""
@@ -164,6 +183,11 @@ class AlarmControlPanel(Entity):
""" """
return self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code) return self.hass.async_add_executor_job(self.alarm_arm_custom_bypass, code)
@property
@abstractmethod
def supported_features(self) -> int:
"""Return the list of supported features."""
@property @property
def state_attributes(self): def state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""

View File

@@ -0,0 +1,7 @@
"""Provides the constants needed for component."""
SUPPORT_ALARM_ARM_HOME = 1
SUPPORT_ALARM_ARM_AWAY = 2
SUPPORT_ALARM_ARM_NIGHT = 4
SUPPORT_ALARM_TRIGGER = 8
SUPPORT_ALARM_ARM_CUSTOM_BYPASS = 16

View File

@@ -1,5 +1,6 @@
"""Provides device automations for Alarm control panel.""" """Provides device automations for Alarm control panel."""
from typing import Optional, List from typing import List, Optional
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
@@ -16,10 +17,17 @@ from homeassistant.const import (
SERVICE_ALARM_DISARM, SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER, SERVICE_ALARM_TRIGGER,
) )
from homeassistant.core import HomeAssistant, Context from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import ATTR_CODE_ARM_REQUIRED, DOMAIN from . import ATTR_CODE_ARM_REQUIRED, DOMAIN
from .const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_TRIGGER,
)
ACTION_TYPES = {"arm_away", "arm_home", "arm_night", "disarm", "trigger"} ACTION_TYPES = {"arm_away", "arm_home", "arm_night", "disarm", "trigger"}
@@ -42,7 +50,16 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
if entry.domain != DOMAIN: if entry.domain != DOMAIN:
continue continue
state = hass.states.get(entry.entity_id)
# We need a state or else we can't populate the HVAC and preset modes.
if state is None:
continue
supported_features = state.attributes["supported_features"]
# Add actions for each entity that belongs to this integration # Add actions for each entity that belongs to this integration
if supported_features & SUPPORT_ALARM_ARM_AWAY:
actions.append( actions.append(
{ {
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
@@ -51,6 +68,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
CONF_TYPE: "arm_away", CONF_TYPE: "arm_away",
} }
) )
if supported_features & SUPPORT_ALARM_ARM_HOME:
actions.append( actions.append(
{ {
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
@@ -59,6 +77,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
CONF_TYPE: "arm_home", CONF_TYPE: "arm_home",
} }
) )
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
actions.append( actions.append(
{ {
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,
@@ -75,6 +94,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
CONF_TYPE: "disarm", CONF_TYPE: "disarm",
} }
) )
if supported_features & SUPPORT_ALARM_TRIGGER:
actions.append( actions.append(
{ {
CONF_DEVICE_ID: device_id, CONF_DEVICE_ID: device_id,

View File

@@ -0,0 +1,151 @@
"""Provides device automations for Alarm control panel."""
from typing import List
import voluptuous as vol
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
)
from homeassistant.components.automation import AutomationActionType, state
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_TYPES = {
"triggered",
"disarmed",
"armed_home",
"armed_away",
"armed_night",
}
TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
}
)
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device triggers for Alarm control panel devices."""
registry = await entity_registry.async_get_registry(hass)
triggers = []
# Get all the integrations entities for this device
for entry in entity_registry.async_entries_for_device(registry, device_id):
if entry.domain != DOMAIN:
continue
entity_state = hass.states.get(entry.entity_id)
# We need a state or else we can't populate the HVAC and preset modes.
if entity_state is None:
continue
supported_features = entity_state.attributes["supported_features"]
# Add triggers for each entity that belongs to this integration
triggers += [
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "disarmed",
},
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "triggered",
},
]
if supported_features & SUPPORT_ALARM_ARM_HOME:
triggers.append(
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "armed_home",
}
)
if supported_features & SUPPORT_ALARM_ARM_AWAY:
triggers.append(
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "armed_away",
}
)
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
triggers.append(
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "armed_night",
}
)
return triggers
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
config = TRIGGER_SCHEMA(config)
if config[CONF_TYPE] == "triggered":
from_state = STATE_ALARM_PENDING
to_state = STATE_ALARM_TRIGGERED
elif config[CONF_TYPE] == "disarmed":
from_state = STATE_ALARM_TRIGGERED
to_state = STATE_ALARM_DISARMED
elif config[CONF_TYPE] == "armed_home":
from_state = STATE_ALARM_PENDING
to_state = STATE_ALARM_ARMED_HOME
elif config[CONF_TYPE] == "armed_away":
from_state = STATE_ALARM_PENDING
to_state = STATE_ALARM_ARMED_AWAY
elif config[CONF_TYPE] == "armed_night":
from_state = STATE_ALARM_PENDING
to_state = STATE_ALARM_ARMED_NIGHT
state_config = {
state.CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state.CONF_FROM: from_state,
state.CONF_TO: to_state,
}
state_config = state.TRIGGER_SCHEMA(state_config)
return await state.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)

View File

@@ -4,7 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/alarm_control_panel", "documentation": "https://www.home-assistant.io/integrations/alarm_control_panel",
"requirements": [], "requirements": [],
"dependencies": [], "dependencies": [],
"codeowners": [ "codeowners": []
"@colinodell"
]
} }

View File

@@ -59,85 +59,3 @@ alarm_trigger:
code: code:
description: An optional code to trigger the alarm control panel with. description: An optional code to trigger the alarm control panel with.
example: 1234 example: 1234
envisalink_alarm_keypress:
description: Send custom keypresses to the alarm.
fields:
entity_id:
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
keypress:
description: 'String to send to the alarm panel (1-6 characters).'
example: '*71'
alarmdecoder_alarm_toggle_chime:
description: Send the alarm the toggle chime command.
fields:
entity_id:
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
code:
description: A required code to toggle the alarm control panel chime with.
example: 1234
ifttt_push_alarm_state:
description: Update the alarm state to the specified value.
fields:
entity_id:
description: Name of the alarm control panel which state has to be updated.
example: 'alarm_control_panel.downstairs'
state:
description: The state to which the alarm control panel has to be set.
example: 'armed_night'
elkm1_alarm_arm_vacation:
description: Arm the ElkM1 in vacation mode.
fields:
entity_id:
description: Name of alarm control panel to arm.
example: 'alarm_control_panel.main'
code:
description: An code to arm the alarm control panel.
example: 1234
elkm1_alarm_arm_home_instant:
description: Arm the ElkM1 in home instant mode.
fields:
entity_id:
description: Name of alarm control panel to arm.
example: 'alarm_control_panel.main'
code:
description: An code to arm the alarm control panel.
example: 1234
elkm1_alarm_arm_night_instant:
description: Arm the ElkM1 in night instant mode.
fields:
entity_id:
description: Name of alarm control panel to arm.
example: 'alarm_control_panel.main'
code:
description: An code to arm the alarm control panel.
example: 1234
elkm1_alarm_display_message:
description: Display a message on all of the ElkM1 keypads for an area.
fields:
entity_id:
description: Name of alarm control panel to display messages on.
example: 'alarm_control_panel.main'
clear:
description: 0=clear message, 1=clear message with * key, 2=Display until timeout; default 2
example: 1
beep:
description: 0=no beep, 1=beep; default 0
example: 1
timeout:
description: Time to display message, 0=forever, max 65535, default 0
example: 4242
line1:
description: Up to 16 characters of text (truncated if too long). Default blank.
example: The answer to life,
line2:
description: Up to 16 characters of text (truncated if too long). Default blank.
example: the universe, and everything.

View File

@@ -6,6 +6,13 @@
"arm_night": "Arm {entity_name} night", "arm_night": "Arm {entity_name} night",
"disarm": "Disarm {entity_name}", "disarm": "Disarm {entity_name}",
"trigger": "Trigger {entity_name}" "trigger": "Trigger {entity_name}"
},
"trigger_type": {
"triggered": "{entity_name} triggered",
"disarmed": "{entity_name} disarmed",
"armed_home": "{entity_name} armed home",
"armed_away": "{entity_name} armed away",
"armed_night": "{entity_name} armed night"
} }
} }
} }

View File

@@ -1,14 +1,17 @@
"""Support for AlarmDecoder devices.""" """Support for AlarmDecoder devices."""
from datetime import timedelta
import logging import logging
from datetime import timedelta from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import SerialDevice, SocketDevice, USBDevice
from alarmdecoder.util import NoDeviceError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST
from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.discovery import load_platform
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -109,9 +112,6 @@ CONFIG_SCHEMA = vol.Schema(
def setup(hass, config): def setup(hass, config):
"""Set up for the AlarmDecoder devices.""" """Set up for the AlarmDecoder devices."""
from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import SocketDevice, SerialDevice, USBDevice
conf = config.get(DOMAIN) conf = config.get(DOMAIN)
restart = False restart = False
@@ -134,8 +134,6 @@ def setup(hass, config):
def open_connection(now=None): def open_connection(now=None):
"""Open a connection to AlarmDecoder.""" """Open a connection to AlarmDecoder."""
from alarmdecoder.util import NoDeviceError
nonlocal restart nonlocal restart
try: try:
controller.open(baud) controller.open(baud)

View File

@@ -3,7 +3,15 @@ import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import (
FORMAT_NUMBER,
AlarmControlPanel,
)
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
@@ -13,11 +21,11 @@ from homeassistant.const import (
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DATA_AD, DOMAIN as DOMAIN_ALARMDECODER, SIGNAL_PANEL_MESSAGE from . import DATA_AD, DOMAIN, SIGNAL_PANEL_MESSAGE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SERVICE_ALARM_TOGGLE_CHIME = "alarmdecoder_alarm_toggle_chime" SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime"
ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string}) ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string})
SERVICE_ALARM_KEYPRESS = "alarm_keypress" SERVICE_ALARM_KEYPRESS = "alarm_keypress"
@@ -36,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.alarm_toggle_chime(code) device.alarm_toggle_chime(code)
hass.services.register( hass.services.register(
alarm.DOMAIN, DOMAIN,
SERVICE_ALARM_TOGGLE_CHIME, SERVICE_ALARM_TOGGLE_CHIME,
alarm_toggle_chime_handler, alarm_toggle_chime_handler,
schema=ALARM_TOGGLE_CHIME_SCHEMA, schema=ALARM_TOGGLE_CHIME_SCHEMA,
@@ -48,14 +56,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.alarm_keypress(keypress) device.alarm_keypress(keypress)
hass.services.register( hass.services.register(
DOMAIN_ALARMDECODER, DOMAIN,
SERVICE_ALARM_KEYPRESS, SERVICE_ALARM_KEYPRESS,
alarm_keypress_handler, alarm_keypress_handler,
schema=ALARM_KEYPRESS_SCHEMA, schema=ALARM_KEYPRESS_SCHEMA,
) )
class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): class AlarmDecoderAlarmPanel(AlarmControlPanel):
"""Representation of an AlarmDecoder-based alarm panel.""" """Representation of an AlarmDecoder-based alarm panel."""
def __init__(self): def __init__(self):
@@ -115,13 +123,18 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
@property @property
def code_format(self): def code_format(self):
"""Return one or more digits/characters.""" """Return one or more digits/characters."""
return alarm.FORMAT_NUMBER return FORMAT_NUMBER
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
return self._state return self._state
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""

View File

@@ -2,9 +2,7 @@
"domain": "alarmdecoder", "domain": "alarmdecoder",
"name": "Alarmdecoder", "name": "Alarmdecoder",
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
"requirements": [ "requirements": ["alarmdecoder==1.13.9"],
"alarmdecoder==1.13.2"
],
"dependencies": [], "dependencies": [],
"codeowners": [] "codeowners": []
} }

View File

@@ -7,3 +7,13 @@ alarm_keypress:
keypress: keypress:
description: 'String to send to the alarm panel.' description: 'String to send to the alarm panel.'
example: '*71' example: '*71'
alarm_toggle_chime:
description: Send the alarm the toggle chime command.
fields:
entity_id:
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
code:
description: A required code to toggle the alarm control panel chime with.
example: 1234

View File

@@ -7,6 +7,10 @@ import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_CODE, CONF_CODE,
CONF_NAME, CONF_NAME,
@@ -95,6 +99,11 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_AWAY return STATE_ALARM_ARMED_AWAY
return None return None
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""

View File

@@ -1,30 +1,30 @@
"""Support for repeating alerts when conditions are met.""" """Support for repeating alerts when conditions are met."""
import asyncio import asyncio
import logging
from datetime import timedelta from datetime import timedelta
import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import ( from homeassistant.components.notify import (
ATTR_DATA,
ATTR_MESSAGE, ATTR_MESSAGE,
ATTR_TITLE, ATTR_TITLE,
ATTR_DATA,
DOMAIN as DOMAIN_NOTIFY, DOMAIN as DOMAIN_NOTIFY,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_ENTITY_ID, CONF_ENTITY_ID,
STATE_IDLE,
CONF_NAME, CONF_NAME,
CONF_STATE, CONF_STATE,
STATE_ON,
STATE_OFF,
SERVICE_TURN_ON,
SERVICE_TURN_OFF,
SERVICE_TOGGLE, SERVICE_TOGGLE,
ATTR_ENTITY_ID, SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_IDLE,
STATE_OFF,
STATE_ON,
) )
from homeassistant.helpers import service, event from homeassistant.helpers import event, service
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.util.dt import now from homeassistant.util.dt import now

View File

@@ -3,25 +3,24 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entityfilter
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.helpers import config_validation as cv, entityfilter
from . import flash_briefings, intent, smart_home_http from . import flash_briefings, intent, smart_home_http
from .const import ( from .const import (
CONF_AUDIO, CONF_AUDIO,
CONF_CLIENT_ID, CONF_CLIENT_ID,
CONF_CLIENT_SECRET, CONF_CLIENT_SECRET,
CONF_DESCRIPTION,
CONF_DISPLAY_CATEGORIES,
CONF_DISPLAY_URL, CONF_DISPLAY_URL,
CONF_ENDPOINT, CONF_ENDPOINT,
CONF_ENTITY_CONFIG,
CONF_FILTER,
CONF_TEXT, CONF_TEXT,
CONF_TITLE, CONF_TITLE,
CONF_UID, CONF_UID,
DOMAIN, DOMAIN,
CONF_FILTER,
CONF_ENTITY_CONFIG,
CONF_DESCRIPTION,
CONF_DISPLAY_CATEGORIES,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -1,8 +1,9 @@
"""Support for Alexa skill auth.""" """Support for Alexa skill auth."""
import asyncio import asyncio
from datetime import timedelta
import json import json
import logging import logging
from datetime import timedelta
import aiohttp import aiohttp
import async_timeout import async_timeout
@@ -50,7 +51,7 @@ class Auth:
"client_secret": self.client_secret, "client_secret": self.client_secret,
} }
_LOGGER.debug( _LOGGER.debug(
"Calling LWA to get the access token (first time), " "with: %s", "Calling LWA to get the access token (first time), with: %s",
json.dumps(lwa_params), json.dumps(lwa_params),
) )

View File

@@ -1,6 +1,10 @@
"""Alexa capabilities.""" """Alexa capabilities."""
import logging import logging
from homeassistant.components import cover, fan, image_processing, input_number, light
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
import homeassistant.components.climate.const as climate
import homeassistant.components.media_player.const as media_player
from homeassistant.const import ( from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
@@ -18,23 +22,26 @@ from homeassistant.const import (
STATE_UNKNOWN, STATE_UNKNOWN,
STATE_UNLOCKED, STATE_UNLOCKED,
) )
import homeassistant.components.climate.const as climate
import homeassistant.components.media_player.const as media_player
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
from homeassistant.components import light, fan, cover
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import ( from .const import (
Catalog,
API_TEMP_UNITS, API_TEMP_UNITS,
API_THERMOSTAT_MODES, API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS, API_THERMOSTAT_PRESETS,
DATE_FORMAT, DATE_FORMAT,
PERCENTAGE_FAN_MAP, PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP, RANGE_FAN_MAP,
Inputs,
) )
from .errors import UnsupportedProperty from .errors import UnsupportedProperty
from .resources import (
AlexaCapabilityResource,
AlexaGlobalCatalog,
AlexaModeResource,
AlexaPresetResource,
AlexaSemantics,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -105,12 +112,41 @@ class AlexaCapability:
@staticmethod @staticmethod
def capability_resources(): def capability_resources():
"""Applicable to ToggleController, RangeController, and ModeController interfaces.""" """Return the capability object.
Applicable to ToggleController, RangeController, and ModeController interfaces.
"""
return [] return []
@staticmethod @staticmethod
def configuration(): def configuration():
"""Return the Configuration object.""" """Return the configuration object.
Applicable to the ThermostatController, SecurityControlPanel, ModeController, RangeController,
and EventDetectionSensor.
"""
return []
@staticmethod
def configurations():
"""Return the configurations object.
The plural configurations object is different that the singular configuration object.
Applicable to EqualizerController interface.
"""
return []
@staticmethod
def inputs():
"""Applicable only to media players."""
return []
@staticmethod
def semantics():
"""Return the semantics object.
Applicable to ToggleController, RangeController, and ModeController interfaces.
"""
return [] return []
@staticmethod @staticmethod
@@ -122,6 +158,10 @@ class AlexaCapability:
"""Serialize according to the Discovery API.""" """Serialize according to the Discovery API."""
result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"} result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"}
instance = self.instance
if instance is not None:
result["instance"] = instance
properties_supported = self.properties_supported() properties_supported = self.properties_supported()
if properties_supported: if properties_supported:
result["properties"] = { result["properties"] = {
@@ -130,22 +170,19 @@ class AlexaCapability:
"retrievable": self.properties_retrievable(), "retrievable": self.properties_retrievable(),
} }
# pylint: disable=assignment-from-none
proactively_reported = self.capability_proactively_reported() proactively_reported = self.capability_proactively_reported()
if proactively_reported is not None: if proactively_reported is not None:
result["proactivelyReported"] = proactively_reported result["proactivelyReported"] = proactively_reported
# pylint: disable=assignment-from-none
non_controllable = self.properties_non_controllable() non_controllable = self.properties_non_controllable()
if non_controllable is not None: if non_controllable is not None:
result["properties"]["nonControllable"] = non_controllable result["properties"]["nonControllable"] = non_controllable
# pylint: disable=assignment-from-none
supports_deactivation = self.supports_deactivation() supports_deactivation = self.supports_deactivation()
if supports_deactivation is not None: if supports_deactivation is not None:
result["supportsDeactivation"] = supports_deactivation result["supportsDeactivation"] = supports_deactivation
capability_resources = self.serialize_capability_resources() capability_resources = self.capability_resources()
if capability_resources: if capability_resources:
result["capabilityResources"] = capability_resources result["capabilityResources"] = capability_resources
@@ -153,15 +190,23 @@ class AlexaCapability:
if configuration: if configuration:
result["configuration"] = configuration result["configuration"] = configuration
# pylint: disable=assignment-from-none # The plural configurations object is different than the singular configuration object above.
instance = self.instance configurations = self.configurations()
if instance is not None: if configurations:
result["instance"] = instance result["configurations"] = configurations
semantics = self.semantics()
if semantics:
result["semantics"] = semantics
supported_operations = self.supported_operations() supported_operations = self.supported_operations()
if supported_operations: if supported_operations:
result["supportedOperations"] = supported_operations result["supportedOperations"] = supported_operations
inputs = self.inputs()
if inputs:
result["inputs"] = inputs
return result return result
def serialize_properties(self): def serialize_properties(self):
@@ -184,35 +229,19 @@ class AlexaCapability:
yield result yield result
def serialize_capability_resources(self):
"""Return capabilityResources friendlyNames serialized for an API response."""
resources = self.capability_resources()
if resources:
return {"friendlyNames": self.serialize_friendly_names(resources)}
return None class Alexa(AlexaCapability):
"""Implements Alexa Interface.
@staticmethod Although endpoints implement this interface implicitly,
def serialize_friendly_names(resources): The API suggests you should explicitly include this interface.
"""Return capabilityResources, ModeResources, or presetResources friendlyNames serialized for an API response."""
friendly_names = []
for resource in resources:
if resource["type"] == Catalog.LABEL_ASSET:
friendly_names.append(
{
"@type": Catalog.LABEL_ASSET,
"value": {"assetId": resource["value"]},
}
)
else:
friendly_names.append(
{
"@type": Catalog.LABEL_TEXT,
"value": {"text": resource["value"], "locale": "en-US"},
}
)
return friendly_names https://developer.amazon.com/docs/device-apis/alexa-interface.html
"""
def name(self):
"""Return the Alexa API name of this interface."""
return "Alexa"
class AlexaEndpointHealth(AlexaCapability): class AlexaEndpointHealth(AlexaCapability):
@@ -236,7 +265,7 @@ class AlexaEndpointHealth(AlexaCapability):
def properties_proactively_reported(self): def properties_proactively_reported(self):
"""Return True if properties asynchronously reported.""" """Return True if properties asynchronously reported."""
return False return True
def properties_retrievable(self): def properties_retrievable(self):
"""Return True if properties can be retrieved.""" """Return True if properties can be retrieved."""
@@ -529,6 +558,23 @@ class AlexaInputController(AlexaCapability):
"""Return the Alexa API name of this interface.""" """Return the Alexa API name of this interface."""
return "Alexa.InputController" return "Alexa.InputController"
def inputs(self):
"""Return the list of valid supported inputs."""
source_list = self.entity.attributes.get(
media_player.ATTR_INPUT_SOURCE_LIST, []
)
input_list = []
for source in source_list:
formatted_source = (
source.lower().replace("-", "").replace("_", "").replace(" ", "")
)
if formatted_source in Inputs.VALID_SOURCE_NAME_MAP.keys():
input_list.append(
{"name": Inputs.VALID_SOURCE_NAME_MAP[formatted_source]}
)
return input_list
class AlexaTemperatureSensor(AlexaCapability): class AlexaTemperatureSensor(AlexaCapability):
"""Implements Alexa.TemperatureSensor. """Implements Alexa.TemperatureSensor.
@@ -752,6 +798,7 @@ class AlexaThermostatController(AlexaCapability):
supported_modes.append(thermostat_mode) supported_modes.append(thermostat_mode)
preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES) preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES)
if preset_modes:
for mode in preset_modes: for mode in preset_modes:
thermostat_mode = API_THERMOSTAT_PRESETS.get(mode) thermostat_mode = API_THERMOSTAT_PRESETS.get(mode)
if thermostat_mode: if thermostat_mode:
@@ -862,6 +909,8 @@ class AlexaModeController(AlexaCapability):
def __init__(self, entity, instance, non_controllable=False): def __init__(self, entity, instance, non_controllable=False):
"""Initialize the entity.""" """Initialize the entity."""
super().__init__(entity, instance) super().__init__(entity, instance)
self._resource = None
self._semantics = None
self.properties_non_controllable = lambda: non_controllable self.properties_non_controllable = lambda: non_controllable
def name(self): def name(self):
@@ -878,73 +927,102 @@ class AlexaModeController(AlexaCapability):
def properties_retrievable(self): def properties_retrievable(self):
"""Return True if properties can be retrieved.""" """Return True if properties can be retrieved."""
return True
def get_property(self, name): def get_property(self, name):
"""Read and return a property.""" """Read and return a property."""
if name != "mode": if name != "mode":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
# Fan Direction
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
return self.entity.attributes.get(fan.ATTR_DIRECTION) mode = self.entity.attributes.get(fan.ATTR_DIRECTION, None)
if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN):
return f"{fan.ATTR_DIRECTION}.{mode}"
# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
# Return state instead of position when using ModeController.
mode = self.entity.state
if mode in (
cover.STATE_OPEN,
cover.STATE_OPENING,
cover.STATE_CLOSED,
cover.STATE_CLOSING,
STATE_UNKNOWN,
):
return f"{cover.ATTR_POSITION}.{mode}"
return None return None
def configuration(self): def configuration(self):
"""Return configuration with modeResources.""" """Return configuration with modeResources."""
return self.serialize_mode_resources() if isinstance(self._resource, AlexaCapabilityResource):
return self._resource.serialize_configuration()
return None
def capability_resources(self): def capability_resources(self):
"""Return capabilityResources object.""" """Return capabilityResources object."""
capability_resources = []
# Fan Direction Resource
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
capability_resources = [ self._resource = AlexaModeResource(
{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_DIRECTION} [AlexaGlobalCatalog.SETTING_DIRECTION], False
] )
self._resource.add_mode(
f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_FORWARD}", [fan.DIRECTION_FORWARD]
)
self._resource.add_mode(
f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_REVERSE}", [fan.DIRECTION_REVERSE]
)
return self._resource.serialize_capability_resources()
return capability_resources # Cover Position Resources
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
self._resource = AlexaModeResource(
["Position", AlexaGlobalCatalog.SETTING_OPENING], False
)
self._resource.add_mode(
f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}",
[AlexaGlobalCatalog.VALUE_OPEN],
)
self._resource.add_mode(
f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}",
[AlexaGlobalCatalog.VALUE_CLOSE],
)
self._resource.add_mode(f"{cover.ATTR_POSITION}.custom", ["Custom"])
return self._resource.serialize_capability_resources()
def mode_resources(self): return None
"""Return modeResources object."""
mode_resources = None
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
mode_resources = {
"ordered": False,
"resources": [
{
"value": f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_FORWARD}",
"friendly_names": [
{"type": Catalog.LABEL_TEXT, "value": fan.DIRECTION_FORWARD}
],
},
{
"value": f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_REVERSE}",
"friendly_names": [
{"type": Catalog.LABEL_TEXT, "value": fan.DIRECTION_REVERSE}
],
},
],
}
return mode_resources def semantics(self):
"""Build and return semantics object."""
def serialize_mode_resources(self): # Cover Position
"""Return ModeResources, friendlyNames serialized for an API response.""" if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
mode_resources = [] self._semantics = AlexaSemantics()
resources = self.mode_resources() self._semantics.add_action_to_directive(
ordered = resources["ordered"] [AlexaSemantics.ACTION_CLOSE, AlexaSemantics.ACTION_LOWER],
for resource in resources["resources"]: "SetMode",
mode_value = resource["value"] {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}"},
friendly_names = resource["friendly_names"] )
result = { self._semantics.add_action_to_directive(
"value": mode_value, [AlexaSemantics.ACTION_OPEN, AlexaSemantics.ACTION_RAISE],
"modeResources": { "SetMode",
"friendlyNames": self.serialize_friendly_names(friendly_names) {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}"},
}, )
} self._semantics.add_states_to_value(
mode_resources.append(result) [AlexaSemantics.STATES_CLOSED],
f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}",
)
self._semantics.add_states_to_value(
[AlexaSemantics.STATES_OPEN],
f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}",
)
return self._semantics.serialize_semantics()
return {"ordered": ordered, "supportedModes": mode_resources} return None
class AlexaRangeController(AlexaCapability): class AlexaRangeController(AlexaCapability):
@@ -956,6 +1034,8 @@ class AlexaRangeController(AlexaCapability):
def __init__(self, entity, instance, non_controllable=False): def __init__(self, entity, instance, non_controllable=False):
"""Initialize the entity.""" """Initialize the entity."""
super().__init__(entity, instance) super().__init__(entity, instance)
self._resource = None
self._semantics = None
self.properties_non_controllable = lambda: non_controllable self.properties_non_controllable = lambda: non_controllable
def name(self): def name(self):
@@ -979,88 +1059,137 @@ class AlexaRangeController(AlexaCapability):
if name != "rangeValue": if name != "rangeValue":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
# Fan Speed
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
speed = self.entity.attributes.get(fan.ATTR_SPEED) speed = self.entity.attributes.get(fan.ATTR_SPEED)
return RANGE_FAN_MAP.get(speed, 0) return RANGE_FAN_MAP.get(speed, 0)
# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION)
# Cover Tilt Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
return self.entity.attributes.get(cover.ATTR_CURRENT_TILT_POSITION)
# Input Number Value
if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
return float(self.entity.state)
return None return None
def configuration(self): def configuration(self):
"""Return configuration with presetResources.""" """Return configuration with presetResources."""
return self.serialize_preset_resources() if isinstance(self._resource, AlexaCapabilityResource):
return self._resource.serialize_configuration()
return None
def capability_resources(self): def capability_resources(self):
"""Return capabilityResources object.""" """Return capabilityResources object."""
capability_resources = []
# Fan Speed Resources
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
return [{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_FANSPEED}] self._resource = AlexaPresetResource(
labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED],
return capability_resources min_value=1,
max_value=3,
def preset_resources(self): precision=1,
"""Return presetResources object."""
preset_resources = []
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
preset_resources = {
"minimumValue": 1,
"maximumValue": 3,
"precision": 1,
"presets": [
{
"rangeValue": 1,
"names": [
{
"type": Catalog.LABEL_ASSET,
"value": Catalog.VALUE_MINIMUM,
},
{"type": Catalog.LABEL_ASSET, "value": Catalog.VALUE_LOW},
],
},
{
"rangeValue": 2,
"names": [
{"type": Catalog.LABEL_ASSET, "value": Catalog.VALUE_MEDIUM}
],
},
{
"rangeValue": 3,
"names": [
{
"type": Catalog.LABEL_ASSET,
"value": Catalog.VALUE_MAXIMUM,
},
{"type": Catalog.LABEL_ASSET, "value": Catalog.VALUE_HIGH},
],
},
],
}
return preset_resources
def serialize_preset_resources(self):
"""Return PresetResources, friendlyNames serialized for an API response."""
preset_resources = []
resources = self.preset_resources()
for preset in resources["presets"]:
preset_resources.append(
{
"rangeValue": preset["rangeValue"],
"presetResources": {
"friendlyNames": self.serialize_friendly_names(preset["names"])
},
}
) )
self._resource.add_preset(
value=1,
labels=[AlexaGlobalCatalog.VALUE_LOW, AlexaGlobalCatalog.VALUE_MINIMUM],
)
self._resource.add_preset(value=2, labels=[AlexaGlobalCatalog.VALUE_MEDIUM])
self._resource.add_preset(
value=3,
labels=[
AlexaGlobalCatalog.VALUE_HIGH,
AlexaGlobalCatalog.VALUE_MAXIMUM,
],
)
return self._resource.serialize_capability_resources()
return { # Cover Position Resources
"supportedRange": { if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
"minimumValue": resources["minimumValue"], self._resource = AlexaPresetResource(
"maximumValue": resources["maximumValue"], ["Position", AlexaGlobalCatalog.SETTING_OPENING],
"precision": resources["precision"], min_value=0,
}, max_value=100,
"presets": preset_resources, precision=1,
} unit=AlexaGlobalCatalog.UNIT_PERCENT,
)
return self._resource.serialize_capability_resources()
# Cover Tilt Position Resources
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
self._resource = AlexaPresetResource(
["Tilt Position", AlexaGlobalCatalog.SETTING_OPENING],
min_value=0,
max_value=100,
precision=1,
unit=AlexaGlobalCatalog.UNIT_PERCENT,
)
return self._resource.serialize_capability_resources()
# Input Number Value
if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
min_value = float(self.entity.attributes[input_number.ATTR_MIN])
max_value = float(self.entity.attributes[input_number.ATTR_MAX])
precision = float(self.entity.attributes.get(input_number.ATTR_STEP, 1))
unit = self.entity.attributes.get(input_number.ATTR_UNIT_OF_MEASUREMENT)
self._resource = AlexaPresetResource(
["Value"],
min_value=min_value,
max_value=max_value,
precision=precision,
unit=unit,
)
self._resource.add_preset(
value=min_value, labels=[AlexaGlobalCatalog.VALUE_MINIMUM]
)
self._resource.add_preset(
value=max_value, labels=[AlexaGlobalCatalog.VALUE_MAXIMUM]
)
return self._resource.serialize_capability_resources()
return None
def semantics(self):
"""Build and return semantics object."""
# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
self._semantics = AlexaSemantics()
self._semantics.add_action_to_directive(
[AlexaSemantics.ACTION_LOWER], "SetRangeValue", {"rangeValue": 0}
)
self._semantics.add_action_to_directive(
[AlexaSemantics.ACTION_RAISE], "SetRangeValue", {"rangeValue": 100}
)
self._semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0)
self._semantics.add_states_to_range(
[AlexaSemantics.STATES_OPEN], min_value=1, max_value=100
)
return self._semantics.serialize_semantics()
# Cover Tilt Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
self._semantics = AlexaSemantics()
self._semantics.add_action_to_directive(
[AlexaSemantics.ACTION_CLOSE], "SetRangeValue", {"rangeValue": 0}
)
self._semantics.add_action_to_directive(
[AlexaSemantics.ACTION_OPEN], "SetRangeValue", {"rangeValue": 100}
)
self._semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0)
self._semantics.add_states_to_range(
[AlexaSemantics.STATES_OPEN], min_value=1, max_value=100
)
return self._semantics.serialize_semantics()
return None
class AlexaToggleController(AlexaCapability): class AlexaToggleController(AlexaCapability):
@@ -1072,6 +1201,8 @@ class AlexaToggleController(AlexaCapability):
def __init__(self, entity, instance, non_controllable=False): def __init__(self, entity, instance, non_controllable=False):
"""Initialize the entity.""" """Initialize the entity."""
super().__init__(entity, instance) super().__init__(entity, instance)
self._resource = None
self._semantics = None
self.properties_non_controllable = lambda: non_controllable self.properties_non_controllable = lambda: non_controllable
def name(self): def name(self):
@@ -1095,6 +1226,7 @@ class AlexaToggleController(AlexaCapability):
if name != "toggleState": if name != "toggleState":
raise UnsupportedProperty(name) raise UnsupportedProperty(name)
# Fan Oscillating
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
is_on = bool(self.entity.attributes.get(fan.ATTR_OSCILLATING)) is_on = bool(self.entity.attributes.get(fan.ATTR_OSCILLATING))
return "ON" if is_on else "OFF" return "ON" if is_on else "OFF"
@@ -1103,16 +1235,15 @@ class AlexaToggleController(AlexaCapability):
def capability_resources(self): def capability_resources(self):
"""Return capabilityResources object.""" """Return capabilityResources object."""
capability_resources = []
# Fan Oscillating Resource
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
capability_resources = [ self._resource = AlexaCapabilityResource(
{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_OSCILLATE}, [AlexaGlobalCatalog.SETTING_OSCILLATE, "Rotate", "Rotation"]
{"type": Catalog.LABEL_TEXT, "value": "Rotate"}, )
{"type": Catalog.LABEL_TEXT, "value": "Rotation"}, return self._resource.serialize_capability_resources()
]
return capability_resources return None
class AlexaChannelController(AlexaCapability): class AlexaChannelController(AlexaCapability):
@@ -1186,3 +1317,112 @@ class AlexaSeekController(AlexaCapability):
def name(self): def name(self):
"""Return the Alexa API name of this interface.""" """Return the Alexa API name of this interface."""
return "Alexa.SeekController" return "Alexa.SeekController"
class AlexaEventDetectionSensor(AlexaCapability):
"""Implements Alexa.EventDetectionSensor.
https://developer.amazon.com/docs/device-apis/alexa-eventdetectionsensor.html
"""
def __init__(self, hass, entity):
"""Initialize the entity."""
super().__init__(entity)
self.hass = hass
def name(self):
"""Return the Alexa API name of this interface."""
return "Alexa.EventDetectionSensor"
def properties_supported(self):
"""Return what properties this entity supports."""
return [{"name": "humanPresenceDetectionState"}]
def properties_proactively_reported(self):
"""Return True if properties asynchronously reported."""
return True
def get_property(self, name):
"""Read and return a property."""
if name != "humanPresenceDetectionState":
raise UnsupportedProperty(name)
human_presence = "NOT_DETECTED"
state = self.entity.state
# Return None for unavailable and unknown states.
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return None
if self.entity.domain == image_processing.DOMAIN:
if int(state):
human_presence = "DETECTED"
elif state == STATE_ON:
human_presence = "DETECTED"
return {"value": human_presence}
def configuration(self):
"""Return supported detection types."""
return {
"detectionMethods": ["AUDIO", "VIDEO"],
"detectionModes": {
"humanPresence": {
"featureAvailability": "ENABLED",
"supportsNotDetected": True,
}
},
}
class AlexaEqualizerController(AlexaCapability):
"""Implements Alexa.EqualizerController.
https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-equalizercontroller.html
"""
def name(self):
"""Return the Alexa API name of this interface."""
return "Alexa.EqualizerController"
def properties_supported(self):
"""Return what properties this entity supports.
Either bands, mode or both can be specified. Only mode is supported at this time.
"""
return [{"name": "mode"}]
def get_property(self, name):
"""Read and return a property."""
if name != "mode":
raise UnsupportedProperty(name)
sound_mode = self.entity.attributes.get(media_player.ATTR_SOUND_MODE)
if sound_mode and sound_mode.upper() in (
"MOVIE",
"MUSIC",
"NIGHT",
"SPORT",
"TV",
):
return sound_mode.upper()
return None
def configurations(self):
"""Return the sound modes supported in the configurations object.
Valid Values for modes are: MOVIE, MUSIC, NIGHT, SPORT, TV.
"""
configurations = None
sound_mode_list = self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST)
if sound_mode_list:
supported_sound_modes = []
for sound_mode in sound_mode_list:
if sound_mode.upper() in ("MOVIE", "MUSIC", "NIGHT", "SPORT", "TV"):
supported_sound_modes.append({"name": sound_mode.upper()})
configurations = {"modes": {"supported": supported_sound_modes}}
return configurations

View File

@@ -1,9 +1,9 @@
"""Constants for the Alexa integration.""" """Constants for the Alexa integration."""
from collections import OrderedDict from collections import OrderedDict
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.climate import const as climate
from homeassistant.components import fan from homeassistant.components import fan
from homeassistant.components.climate import const as climate
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
DOMAIN = "alexa" DOMAIN = "alexa"
@@ -117,158 +117,90 @@ class Cause:
VOICE_INTERACTION = "VOICE_INTERACTION" VOICE_INTERACTION = "VOICE_INTERACTION"
class Catalog: class Inputs:
"""The Global Alexa catalog. """Valid names for the InputController.
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#input
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" VALID_SOURCE_NAME_MAP = {
LABEL_TEXT = "text" "aux": "AUX 1",
"aux1": "AUX 1",
"aux2": "AUX 2",
"aux3": "AUX 3",
"aux4": "AUX 4",
"aux5": "AUX 5",
"aux6": "AUX 6",
"aux7": "AUX 7",
"bluray": "BLURAY",
"cable": "CABLE",
"cd": "CD",
"coax": "COAX 1",
"coax1": "COAX 1",
"coax2": "COAX 2",
"composite": "COMPOSITE 1",
"composite1": "COMPOSITE 1",
"dvd": "DVD",
"game": "GAME",
"gameconsole": "GAME",
"hdradio": "HD RADIO",
"hdmi": "HDMI 1",
"hdmi1": "HDMI 1",
"hdmi2": "HDMI 2",
"hdmi3": "HDMI 3",
"hdmi4": "HDMI 4",
"hdmi5": "HDMI 5",
"hdmi6": "HDMI 6",
"hdmi7": "HDMI 7",
"hdmi8": "HDMI 8",
"hdmi9": "HDMI 9",
"hdmi10": "HDMI 10",
"hdmiarc": "HDMI ARC",
"input": "INPUT 1",
"input1": "INPUT 1",
"input2": "INPUT 2",
"input3": "INPUT 3",
"input4": "INPUT 4",
"input5": "INPUT 5",
"input6": "INPUT 6",
"input7": "INPUT 7",
"input8": "INPUT 8",
"input9": "INPUT 9",
"input10": "INPUT 10",
"ipod": "IPOD",
"line": "LINE 1",
"line1": "LINE 1",
"line2": "LINE 2",
"line3": "LINE 3",
"line4": "LINE 4",
"line5": "LINE 5",
"line6": "LINE 6",
"line7": "LINE 7",
"mediaplayer": "MEDIA PLAYER",
"optical": "OPTICAL 1",
"optical1": "OPTICAL 1",
"optical2": "OPTICAL 2",
"phono": "PHONO",
"playstation": "PLAYSTATION",
"playstation3": "PLAYSTATION 3",
"playstation4": "PLAYSTATION 4",
"satellite": "SATELLITE",
"satellitetv": "SATELLITE",
"smartcast": "SMARTCAST",
"tuner": "TUNER",
"tv": "TV",
"usbdac": "USB DAC",
"video": "VIDEO 1",
"video1": "VIDEO 1",
"video2": "VIDEO 2",
"video3": "VIDEO 3",
"xbox": "XBOX",
}
# Shower VALID_SOUND_MODE_MAP = {
DEVICENAME_SHOWER = "Alexa.DeviceName.Shower" "movie": "MOVIE",
"music": "MUSIC",
# Washer, Washing Machine "night": "NIGHT",
DEVICENAME_WASHER = "Alexa.DeviceName.Washer" "sport": "SPORT",
"tv": "TV",
# 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"

View File

@@ -1,7 +1,26 @@
"""Alexa entity adapters.""" """Alexa entity adapters."""
from typing import List from typing import List
from homeassistant.core import callback from homeassistant.components import (
alarm_control_panel,
alert,
automation,
binary_sensor,
cover,
fan,
group,
image_processing,
input_boolean,
input_number,
light,
lock,
media_player,
scene,
script,
sensor,
switch,
)
from homeassistant.components.climate import const as climate
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
@@ -11,28 +30,11 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
) )
from homeassistant.core import callback
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
from homeassistant.components.climate import const as climate
from homeassistant.components import (
alarm_control_panel,
alert,
automation,
binary_sensor,
cover,
fan,
group,
input_boolean,
light,
lock,
media_player,
scene,
script,
sensor,
switch,
)
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
from .capabilities import ( from .capabilities import (
Alexa,
AlexaBrightnessController, AlexaBrightnessController,
AlexaChannelController, AlexaChannelController,
AlexaColorController, AlexaColorController,
@@ -40,6 +42,8 @@ from .capabilities import (
AlexaContactSensor, AlexaContactSensor,
AlexaDoorbellEventSource, AlexaDoorbellEventSource,
AlexaEndpointHealth, AlexaEndpointHealth,
AlexaEqualizerController,
AlexaEventDetectionSensor,
AlexaInputController, AlexaInputController,
AlexaLockController, AlexaLockController,
AlexaModeController, AlexaModeController,
@@ -59,6 +63,7 @@ from .capabilities import (
AlexaThermostatController, AlexaThermostatController,
AlexaToggleController, AlexaToggleController,
) )
from .const import CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES
ENTITY_ADAPTERS = Registry() ENTITY_ADAPTERS = Registry()
@@ -80,6 +85,9 @@ class DisplayCategory:
# Indicates media devices with video or photo capabilities. # Indicates media devices with video or photo capabilities.
CAMERA = "CAMERA" CAMERA = "CAMERA"
# Indicates a non-mobile computer, such as a desktop computer.
COMPUTER = "COMPUTER"
# Indicates an endpoint that detects and reports contact. # Indicates an endpoint that detects and reports contact.
CONTACT_SENSOR = "CONTACT_SENSOR" CONTACT_SENSOR = "CONTACT_SENSOR"
@@ -89,27 +97,60 @@ class DisplayCategory:
# Indicates a doorbell. # Indicates a doorbell.
DOORBELL = "DOORBELL" DOORBELL = "DOORBELL"
# Indicates a window covering on the outside of a structure.
EXTERIOR_BLIND = "EXTERIOR_BLIND"
# Indicates a fan. # Indicates a fan.
FAN = "FAN" FAN = "FAN"
# Indicates a game console, such as Microsoft Xbox or Nintendo Switch
GAME_CONSOLE = "GAME_CONSOLE"
# Indicates a garage door. Garage doors must implement the ModeController interface to open and close the door.
GARAGE_DOOR = "GARAGE_DOOR"
# Indicates a window covering on the inside of a structure.
INTERIOR_BLIND = "INTERIOR_BLIND"
# Indicates a laptop or other mobile computer.
LAPTOP = "LAPTOP"
# Indicates light sources or fixtures. # Indicates light sources or fixtures.
LIGHT = "LIGHT" LIGHT = "LIGHT"
# Indicates a microwave oven. # Indicates a microwave oven.
MICROWAVE = "MICROWAVE" MICROWAVE = "MICROWAVE"
# Indicates a mobile phone.
MOBILE_PHONE = "MOBILE_PHONE"
# Indicates an endpoint that detects and reports motion. # Indicates an endpoint that detects and reports motion.
MOTION_SENSOR = "MOTION_SENSOR" MOTION_SENSOR = "MOTION_SENSOR"
# Indicates a network-connected music system.
MUSIC_SYSTEM = "MUSIC_SYSTEM"
# An endpoint that cannot be described in on of the other categories. # An endpoint that cannot be described in on of the other categories.
OTHER = "OTHER" OTHER = "OTHER"
# Indicates a network router.
NETWORK_HARDWARE = "NETWORK_HARDWARE"
# Indicates an oven cooking appliance.
OVEN = "OVEN"
# Indicates a non-mobile phone, such as landline or an IP phone.
PHONE = "PHONE"
# Describes a combination of devices set to a specific state, when the # Describes a combination of devices set to a specific state, when the
# order of the state change is not important. For example a bedtime scene # order of the state change is not important. For example a bedtime scene
# might include turning off lights and lowering the thermostat, but the # might include turning off lights and lowering the thermostat, but the
# order is unimportant. Applies to Scenes # order is unimportant. Applies to Scenes
SCENE_TRIGGER = "SCENE_TRIGGER" SCENE_TRIGGER = "SCENE_TRIGGER"
# Indicates a projector screen.
SCREEN = "SCREEN"
# Indicates a security panel. # Indicates a security panel.
SECURITY_PANEL = "SECURITY_PANEL" SECURITY_PANEL = "SECURITY_PANEL"
@@ -123,10 +164,16 @@ class DisplayCategory:
# Indicates the endpoint is a speaker or speaker system. # Indicates the endpoint is a speaker or speaker system.
SPEAKER = "SPEAKER" SPEAKER = "SPEAKER"
# Indicates a streaming device such as Apple TV, Chromecast, or Roku.
STREAMING_DEVICE = "STREAMING_DEVICE"
# Indicates in-wall switches wired to the electrical system. Can control a # Indicates in-wall switches wired to the electrical system. Can control a
# variety of devices. # variety of devices.
SWITCH = "SWITCH" SWITCH = "SWITCH"
# Indicates a tablet computer.
TABLET = "TABLET"
# Indicates endpoints that report the temperature only. # Indicates endpoints that report the temperature only.
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR" TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
@@ -137,6 +184,9 @@ class DisplayCategory:
# Indicates the endpoint is a television. # Indicates the endpoint is a television.
TV = "TV" TV = "TV"
# Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear.
WEARABLE = "WEARABLE"
class AlexaEntity: class AlexaEntity:
"""An adaptation of an entity, expressed in Alexa's terms. """An adaptation of an entity, expressed in Alexa's terms.
@@ -261,6 +311,7 @@ class GenericCapabilities(AlexaEntity):
return [ return [
AlexaPowerController(self.entity), AlexaPowerController(self.entity),
AlexaEndpointHealth(self.hass, self.entity), AlexaEndpointHealth(self.hass, self.entity),
Alexa(self.hass),
] ]
@@ -270,6 +321,10 @@ class SwitchCapabilities(AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
"""Return the display categories for this entity.""" """Return the display categories for this entity."""
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
if device_class == switch.DEVICE_CLASS_OUTLET:
return [DisplayCategory.SMARTPLUG]
return [DisplayCategory.SWITCH] return [DisplayCategory.SWITCH]
def interfaces(self): def interfaces(self):
@@ -277,6 +332,7 @@ class SwitchCapabilities(AlexaEntity):
return [ return [
AlexaPowerController(self.entity), AlexaPowerController(self.entity),
AlexaEndpointHealth(self.hass, self.entity), AlexaEndpointHealth(self.hass, self.entity),
Alexa(self.hass),
] ]
@@ -299,6 +355,7 @@ class ClimateCapabilities(AlexaEntity):
yield AlexaThermostatController(self.hass, self.entity) yield AlexaThermostatController(self.hass, self.entity)
yield AlexaTemperatureSensor(self.hass, self.entity) yield AlexaTemperatureSensor(self.hass, self.entity)
yield AlexaEndpointHealth(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(cover.DOMAIN) @ENTITY_ADAPTERS.register(cover.DOMAIN)
@@ -307,15 +364,43 @@ class CoverCapabilities(AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
"""Return the display categories for this entity.""" """Return the display categories for this entity."""
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
if device_class == cover.DEVICE_CLASS_GARAGE:
return [DisplayCategory.GARAGE_DOOR]
if device_class == cover.DEVICE_CLASS_DOOR:
return [DisplayCategory.DOOR] return [DisplayCategory.DOOR]
if device_class in (
cover.DEVICE_CLASS_BLIND,
cover.DEVICE_CLASS_SHADE,
cover.DEVICE_CLASS_CURTAIN,
):
return [DisplayCategory.INTERIOR_BLIND]
if device_class in (
cover.DEVICE_CLASS_WINDOW,
cover.DEVICE_CLASS_AWNING,
cover.DEVICE_CLASS_SHUTTER,
):
return [DisplayCategory.EXTERIOR_BLIND]
return [DisplayCategory.OTHER]
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION: if supported & cover.SUPPORT_SET_POSITION:
yield AlexaPercentageController(self.entity) yield AlexaRangeController(
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 AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(light.DOMAIN) @ENTITY_ADAPTERS.register(light.DOMAIN)
@@ -337,7 +422,9 @@ class LightCapabilities(AlexaEntity):
yield AlexaColorController(self.entity) yield AlexaColorController(self.entity)
if supported & light.SUPPORT_COLOR_TEMP: if supported & light.SUPPORT_COLOR_TEMP:
yield AlexaColorTemperatureController(self.entity) yield AlexaColorTemperatureController(self.entity)
yield AlexaEndpointHealth(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(fan.DOMAIN) @ENTITY_ADAPTERS.register(fan.DOMAIN)
@@ -351,6 +438,7 @@ class FanCapabilities(AlexaEntity):
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
yield AlexaPowerController(self.entity) yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & fan.SUPPORT_SET_SPEED: if supported & fan.SUPPORT_SET_SPEED:
yield AlexaPercentageController(self.entity) yield AlexaPercentageController(self.entity)
@@ -358,7 +446,6 @@ class FanCapabilities(AlexaEntity):
yield AlexaRangeController( yield AlexaRangeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
) )
if supported & fan.SUPPORT_OSCILLATE: if supported & fan.SUPPORT_OSCILLATE:
yield AlexaToggleController( yield AlexaToggleController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
@@ -369,6 +456,7 @@ class FanCapabilities(AlexaEntity):
) )
yield AlexaEndpointHealth(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(lock.DOMAIN) @ENTITY_ADAPTERS.register(lock.DOMAIN)
@@ -384,6 +472,7 @@ class LockCapabilities(AlexaEntity):
return [ return [
AlexaLockController(self.entity), AlexaLockController(self.entity),
AlexaEndpointHealth(self.hass, self.entity), AlexaEndpointHealth(self.hass, self.entity),
Alexa(self.hass),
] ]
@@ -401,7 +490,6 @@ class MediaPlayerCapabilities(AlexaEntity):
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
yield AlexaEndpointHealth(self.hass, self.entity)
yield AlexaPowerController(self.entity) yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
@@ -435,6 +523,12 @@ class MediaPlayerCapabilities(AlexaEntity):
if supported & media_player.const.SUPPORT_PLAY_MEDIA: if supported & media_player.const.SUPPORT_PLAY_MEDIA:
yield AlexaChannelController(self.entity) yield AlexaChannelController(self.entity)
if supported & media_player.const.SUPPORT_SELECT_SOUND_MODE:
yield AlexaEqualizerController(self.entity)
yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(scene.DOMAIN) @ENTITY_ADAPTERS.register(scene.DOMAIN)
class SceneCapabilities(AlexaEntity): class SceneCapabilities(AlexaEntity):
@@ -453,7 +547,10 @@ class SceneCapabilities(AlexaEntity):
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
return [AlexaSceneController(self.entity, supports_deactivation=False)] return [
AlexaSceneController(self.entity, supports_deactivation=False),
Alexa(self.hass),
]
@ENTITY_ADAPTERS.register(script.DOMAIN) @ENTITY_ADAPTERS.register(script.DOMAIN)
@@ -467,7 +564,10 @@ class ScriptCapabilities(AlexaEntity):
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
can_cancel = bool(self.entity.attributes.get("can_cancel")) can_cancel = bool(self.entity.attributes.get("can_cancel"))
return [AlexaSceneController(self.entity, supports_deactivation=can_cancel)] return [
AlexaSceneController(self.entity, supports_deactivation=can_cancel),
Alexa(self.hass),
]
@ENTITY_ADAPTERS.register(sensor.DOMAIN) @ENTITY_ADAPTERS.register(sensor.DOMAIN)
@@ -486,6 +586,7 @@ class SensorCapabilities(AlexaEntity):
if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_FAHRENHEIT, TEMP_CELSIUS): if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_FAHRENHEIT, TEMP_CELSIUS):
yield AlexaTemperatureSensor(self.hass, self.entity) yield AlexaTemperatureSensor(self.hass, self.entity)
yield AlexaEndpointHealth(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@ENTITY_ADAPTERS.register(binary_sensor.DOMAIN) @ENTITY_ADAPTERS.register(binary_sensor.DOMAIN)
@@ -494,6 +595,7 @@ class BinarySensorCapabilities(AlexaEntity):
TYPE_CONTACT = "contact" TYPE_CONTACT = "contact"
TYPE_MOTION = "motion" TYPE_MOTION = "motion"
TYPE_PRESENCE = "presence"
def default_display_categories(self): def default_display_categories(self):
"""Return the display categories for this entity.""" """Return the display categories for this entity."""
@@ -502,6 +604,8 @@ class BinarySensorCapabilities(AlexaEntity):
return [DisplayCategory.CONTACT_SENSOR] return [DisplayCategory.CONTACT_SENSOR]
if sensor_type is self.TYPE_MOTION: if sensor_type is self.TYPE_MOTION:
return [DisplayCategory.MOTION_SENSOR] return [DisplayCategory.MOTION_SENSOR]
if sensor_type is self.TYPE_PRESENCE:
return [DisplayCategory.CAMERA]
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
@@ -510,22 +614,41 @@ class BinarySensorCapabilities(AlexaEntity):
yield AlexaContactSensor(self.hass, self.entity) yield AlexaContactSensor(self.hass, self.entity)
elif sensor_type is self.TYPE_MOTION: elif sensor_type is self.TYPE_MOTION:
yield AlexaMotionSensor(self.hass, self.entity) yield AlexaMotionSensor(self.hass, self.entity)
elif sensor_type is self.TYPE_PRESENCE:
yield AlexaEventDetectionSensor(self.hass, self.entity)
# yield additional interfaces based on specified display category in config.
entity_conf = self.config.entity_config.get(self.entity.entity_id, {}) entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
if CONF_DISPLAY_CATEGORIES in entity_conf: if CONF_DISPLAY_CATEGORIES in entity_conf:
if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL: if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL:
yield AlexaDoorbellEventSource(self.entity) yield AlexaDoorbellEventSource(self.entity)
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.CONTACT_SENSOR:
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 AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
def get_type(self): def get_type(self):
"""Return the type of binary sensor.""" """Return the type of binary sensor."""
attrs = self.entity.attributes attrs = self.entity.attributes
if attrs.get(ATTR_DEVICE_CLASS) in ("door", "garage_door", "opening", "window"): if attrs.get(ATTR_DEVICE_CLASS) in (
binary_sensor.DEVICE_CLASS_DOOR,
binary_sensor.DEVICE_CLASS_GARAGE_DOOR,
binary_sensor.DEVICE_CLASS_OPENING,
binary_sensor.DEVICE_CLASS_WINDOW,
):
return self.TYPE_CONTACT return self.TYPE_CONTACT
if attrs.get(ATTR_DEVICE_CLASS) == "motion":
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_MOTION:
return self.TYPE_MOTION return self.TYPE_MOTION
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_PRESENCE:
return self.TYPE_PRESENCE
@ENTITY_ADAPTERS.register(alarm_control_panel.DOMAIN) @ENTITY_ADAPTERS.register(alarm_control_panel.DOMAIN)
class AlarmControlPanelCapabilities(AlexaEntity): class AlarmControlPanelCapabilities(AlexaEntity):
@@ -540,3 +663,37 @@ class AlarmControlPanelCapabilities(AlexaEntity):
if not self.entity.attributes.get("code_arm_required"): if not self.entity.attributes.get("code_arm_required"):
yield AlexaSecurityPanelController(self.hass, self.entity) yield AlexaSecurityPanelController(self.hass, self.entity)
yield AlexaEndpointHealth(self.hass, self.entity) yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.hass)
@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 logging
import uuid import uuid
import homeassistant.util.dt as dt_util
from homeassistant.components import http from homeassistant.components import http
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import template from homeassistant.helpers import template
import homeassistant.util.dt as dt_util
from .const import ( from .const import (
ATTR_MAIN_TEXT, ATTR_MAIN_TEXT,

View File

@@ -3,13 +3,19 @@ import logging
import math import math
from homeassistant import core as ha from homeassistant import core as ha
from homeassistant.components import cover, fan, group, light, media_player from homeassistant.components import (
cover,
fan,
group,
input_number,
light,
media_player,
)
from homeassistant.components.climate import const as climate from homeassistant.components.climate import const as climate
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
STATE_ALARM_DISARMED,
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_NIGHT,
@@ -21,6 +27,7 @@ from homeassistant.const import (
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_STOP, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
SERVICE_TURN_ON, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_UNLOCK,
@@ -28,23 +35,25 @@ from homeassistant.const import (
SERVICE_VOLUME_MUTE, SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET, SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP, SERVICE_VOLUME_UP,
STATE_ALARM_DISARMED,
TEMP_CELSIUS, TEMP_CELSIUS,
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
) )
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
import homeassistant.util.dt as dt_util
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
import homeassistant.util.dt as dt_util
from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.temperature import convert as convert_temperature
from .const import ( from .const import (
API_TEMP_UNITS, API_TEMP_UNITS,
API_THERMOSTAT_MODES_CUSTOM,
API_THERMOSTAT_MODES, API_THERMOSTAT_MODES,
API_THERMOSTAT_MODES_CUSTOM,
API_THERMOSTAT_PRESETS, API_THERMOSTAT_PRESETS,
Cause,
PERCENTAGE_FAN_MAP, PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP, RANGE_FAN_MAP,
SPEED_FAN_MAP, SPEED_FAN_MAP,
Cause,
Inputs,
) )
from .entities import async_get_entities from .entities import async_get_entities
from .errors import ( from .errors import (
@@ -110,9 +119,7 @@ async def async_api_turn_on(hass, config, directive, context):
domain = ha.DOMAIN domain = ha.DOMAIN
service = SERVICE_TURN_ON service = SERVICE_TURN_ON
if domain == cover.DOMAIN: if domain == media_player.DOMAIN:
service = cover.SERVICE_OPEN_COVER
elif domain == media_player.DOMAIN:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
if not supported & power_features: if not supported & power_features:
@@ -138,9 +145,7 @@ async def async_api_turn_off(hass, config, directive, context):
domain = ha.DOMAIN domain = ha.DOMAIN
service = SERVICE_TURN_OFF service = SERVICE_TURN_OFF
if entity.domain == cover.DOMAIN: if domain == media_player.DOMAIN:
service = cover.SERVICE_CLOSE_COVER
elif domain == media_player.DOMAIN:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
if not supported & power_features: if not supported & power_features:
@@ -345,10 +350,6 @@ async def async_api_set_percentage(hass, config, directive, context):
speed = "high" speed = "high"
data[fan.ATTR_SPEED] = speed data[fan.ATTR_SPEED] = speed
elif entity.domain == cover.DOMAIN:
service = SERVICE_SET_COVER_POSITION
data[cover.ATTR_POSITION] = percentage
await hass.services.async_call( await hass.services.async_call(
entity.domain, service, data, blocking=False, context=context entity.domain, service, data, blocking=False, context=context
) )
@@ -382,13 +383,6 @@ async def async_api_adjust_percentage(hass, config, directive, context):
data[fan.ATTR_SPEED] = speed data[fan.ATTR_SPEED] = speed
elif entity.domain == cover.DOMAIN:
service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION)
data[cover.ATTR_POSITION] = max(0, percentage_delta + current)
await hass.services.async_call( await hass.services.async_call(
entity.domain, service, data, blocking=False, context=context entity.domain, service, data, blocking=False, context=context
) )
@@ -459,13 +453,20 @@ async def async_api_select_input(hass, config, directive, context):
media_input = directive.payload["input"] media_input = directive.payload["input"]
entity = directive.entity entity = directive.entity
# attempt to map the ALL UPPERCASE payload name to a source # Attempt to map the ALL UPPERCASE payload name to a source.
source_list = entity.attributes[media_player.const.ATTR_INPUT_SOURCE_LIST] or [] # Strips trailing 1 to match single input devices.
source_list = entity.attributes.get(media_player.const.ATTR_INPUT_SOURCE_LIST, [])
for source in source_list: for source in source_list:
# response will always be space separated, so format the source in the formatted_source = (
# most likely way to find a match source.lower().replace("-", "").replace("_", "").replace(" ", "")
formatted_source = source.lower().replace("-", " ").replace("_", " ") )
if formatted_source in media_input.lower(): media_input = media_input.lower().replace(" ", "")
if (
formatted_source in Inputs.VALID_SOURCE_NAME_MAP.keys()
and formatted_source == media_input
) or (
media_input.endswith("1") and formatted_source == media_input.rstrip("1")
):
media_input = source media_input = source
break break
else: else:
@@ -950,7 +951,7 @@ async def async_api_disarm(hass, config, directive, context):
@HANDLERS.register(("Alexa.ModeController", "SetMode")) @HANDLERS.register(("Alexa.ModeController", "SetMode"))
async def async_api_set_mode(hass, config, directive, context): async def async_api_set_mode(hass, config, directive, context):
"""Process a next request.""" """Process a SetMode directive."""
entity = directive.entity entity = directive.entity
instance = directive.instance instance = directive.instance
domain = entity.domain domain = entity.domain
@@ -958,46 +959,57 @@ async def async_api_set_mode(hass, config, directive, context):
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
mode = directive.payload["mode"] mode = directive.payload["mode"]
if domain != fan.DOMAIN: # Fan Direction
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
if instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}": if instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
mode, direction = mode.split(".") _, direction = mode.split(".")
if direction in [fan.DIRECTION_REVERSE, fan.DIRECTION_FORWARD]: if direction in (fan.DIRECTION_REVERSE, fan.DIRECTION_FORWARD):
service = fan.SERVICE_SET_DIRECTION service = fan.SERVICE_SET_DIRECTION
data[fan.ATTR_DIRECTION] = direction data[fan.ATTR_DIRECTION] = direction
# Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
_, position = mode.split(".")
if position == cover.STATE_CLOSED:
service = cover.SERVICE_CLOSE_COVER
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( await hass.services.async_call(
domain, service, data, blocking=False, context=context domain, service, data, blocking=False, context=context
) )
return directive.response() response = directive.response()
response.add_context_property(
{
"namespace": "Alexa.ModeController",
"instance": instance,
"name": "mode",
"value": mode,
}
)
return response
@HANDLERS.register(("Alexa.ModeController", "AdjustMode")) @HANDLERS.register(("Alexa.ModeController", "AdjustMode"))
async def async_api_adjust_mode(hass, config, directive, context): async def async_api_adjust_mode(hass, config, directive, context):
"""Process a AdjustMode request. """Process a AdjustMode request.
Requires modeResources to be ordered. Requires capabilityResources supportedModes to be ordered.
Only modes that are ordered support the adjustMode directive. Only supportedModes with ordered=True support the adjustMode directive.
""" """
entity = directive.entity
instance = directive.instance
domain = entity.domain
if domain != fan.DOMAIN: # Currently no supportedModes are configured with ordered=True to support this request.
msg = "Entity does not support directive" msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg) raise AlexaInvalidDirectiveError(msg)
if instance is None:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
# No modeResources are currently ordered to support this request.
return directive.response()
@HANDLERS.register(("Alexa.ToggleController", "TurnOn")) @HANDLERS.register(("Alexa.ToggleController", "TurnOn"))
async def async_api_toggle_on(hass, config, directive, context): async def async_api_toggle_on(hass, config, directive, context):
@@ -1008,19 +1020,29 @@ async def async_api_toggle_on(hass, config, directive, context):
service = None service = None
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
if domain != fan.DOMAIN: # Fan Oscillating
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
service = fan.SERVICE_OSCILLATE service = fan.SERVICE_OSCILLATE
data[fan.ATTR_OSCILLATING] = True data[fan.ATTR_OSCILLATING] = True
else:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
await hass.services.async_call( await hass.services.async_call(
domain, service, data, blocking=False, context=context domain, service, data, blocking=False, context=context
) )
return directive.response() response = directive.response()
response.add_context_property(
{
"namespace": "Alexa.ToggleController",
"instance": instance,
"name": "toggleState",
"value": "ON",
}
)
return response
@HANDLERS.register(("Alexa.ToggleController", "TurnOff")) @HANDLERS.register(("Alexa.ToggleController", "TurnOff"))
@@ -1032,19 +1054,29 @@ async def async_api_toggle_off(hass, config, directive, context):
service = None service = None
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
if domain != fan.DOMAIN: # Fan Oscillating
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}": if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
service = fan.SERVICE_OSCILLATE service = fan.SERVICE_OSCILLATE
data[fan.ATTR_OSCILLATING] = False data[fan.ATTR_OSCILLATING] = False
else:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
await hass.services.async_call( await hass.services.async_call(
domain, service, data, blocking=False, context=context domain, service, data, blocking=False, context=context
) )
return directive.response() response = directive.response()
response.add_context_property(
{
"namespace": "Alexa.ToggleController",
"instance": instance,
"name": "toggleState",
"value": "OFF",
}
)
return response
@HANDLERS.register(("Alexa.RangeController", "SetRangeValue")) @HANDLERS.register(("Alexa.RangeController", "SetRangeValue"))
@@ -1055,15 +1087,12 @@ async def async_api_set_range(hass, config, directive, context):
domain = entity.domain domain = entity.domain
service = None service = None
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
range_value = int(directive.payload["rangeValue"]) range_value = directive.payload["rangeValue"]
if domain != fan.DOMAIN:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
# Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
service = fan.SERVICE_SET_SPEED service = fan.SERVICE_SET_SPEED
speed = SPEED_FAN_MAP.get(range_value, None) speed = SPEED_FAN_MAP.get(int(range_value))
if not speed: if not speed:
msg = "Entity does not support value" msg = "Entity does not support value"
@@ -1074,11 +1103,55 @@ async def async_api_set_range(hass, config, directive, context):
data[fan.ATTR_SPEED] = speed data[fan.ATTR_SPEED] = speed
# Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_value = int(range_value)
if range_value == 0:
service = cover.SERVICE_CLOSE_COVER
elif range_value == 100:
service = cover.SERVICE_OPEN_COVER
else:
service = cover.SERVICE_SET_COVER_POSITION
data[cover.ATTR_POSITION] = range_value
# Cover Tilt Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
range_value = int(range_value)
if range_value == 0:
service = cover.SERVICE_CLOSE_COVER_TILT
elif range_value == 100:
service = cover.SERVICE_OPEN_COVER_TILT
else:
service = cover.SERVICE_SET_COVER_TILT_POSITION
data[cover.ATTR_POSITION] = range_value
# Input Number Value
elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
range_value = float(range_value)
service = input_number.SERVICE_SET_VALUE
min_value = float(entity.attributes[input_number.ATTR_MIN])
max_value = float(entity.attributes[input_number.ATTR_MAX])
data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
else:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
await hass.services.async_call( await hass.services.async_call(
domain, service, data, blocking=False, context=context domain, service, data, blocking=False, context=context
) )
return directive.response() response = directive.response()
response.add_context_property(
{
"namespace": "Alexa.RangeController",
"instance": instance,
"name": "rangeValue",
"value": range_value,
}
)
return response
@HANDLERS.register(("Alexa.RangeController", "AdjustRangeValue")) @HANDLERS.register(("Alexa.RangeController", "AdjustRangeValue"))
@@ -1089,25 +1162,71 @@ async def async_api_adjust_range(hass, config, directive, context):
domain = entity.domain domain = entity.domain
service = None service = None
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
range_delta = int(directive.payload["rangeValueDelta"]) range_delta = directive.payload["rangeValueDelta"]
response_value = 0
# Fan Speed
if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}": if instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
range_delta = int(range_delta)
service = fan.SERVICE_SET_SPEED service = fan.SERVICE_SET_SPEED
# adjust range
current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0) current_range = RANGE_FAN_MAP.get(entity.attributes.get(fan.ATTR_SPEED), 0)
speed = SPEED_FAN_MAP.get(max(0, range_delta + current_range), fan.SPEED_OFF) speed = SPEED_FAN_MAP.get(
min(3, max(0, range_delta + current_range)), fan.SPEED_OFF
)
if speed == fan.SPEED_OFF: if speed == fan.SPEED_OFF:
service = fan.SERVICE_TURN_OFF service = fan.SERVICE_TURN_OFF
data[fan.ATTR_SPEED] = speed data[fan.ATTR_SPEED] = response_value = speed
# Cover Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_delta = int(range_delta)
service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION)
data[cover.ATTR_POSITION] = response_value = min(
100, max(0, range_delta + current)
)
# Cover Tilt Position
elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}":
range_delta = int(range_delta)
service = SERVICE_SET_COVER_TILT_POSITION
current = entity.attributes.get(cover.ATTR_TILT_POSITION)
data[cover.ATTR_TILT_POSITION] = response_value = min(
100, max(0, range_delta + current)
)
# Input Number Value
elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
range_delta = float(range_delta)
service = input_number.SERVICE_SET_VALUE
min_value = float(entity.attributes[input_number.ATTR_MIN])
max_value = float(entity.attributes[input_number.ATTR_MAX])
current = float(entity.state)
data[input_number.ATTR_VALUE] = response_value = min(
max_value, max(min_value, range_delta + current)
)
else:
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)
await hass.services.async_call( await hass.services.async_call(
domain, service, data, blocking=False, context=context domain, service, data, blocking=False, context=context
) )
return directive.response() response = directive.response()
response.add_context_property(
{
"namespace": "Alexa.RangeController",
"instance": instance,
"name": "rangeValue",
"value": response_value,
}
)
return response
@HANDLERS.register(("Alexa.ChannelController", "ChangeChannel")) @HANDLERS.register(("Alexa.ChannelController", "ChangeChannel"))
@@ -1115,21 +1234,25 @@ async def async_api_changechannel(hass, config, directive, context):
"""Process a change channel request.""" """Process a change channel request."""
channel = "0" channel = "0"
entity = directive.entity entity = directive.entity
payload = directive.payload["channel"] channel_payload = directive.payload["channel"]
metadata_payload = directive.payload["channelMetadata"]
payload_name = "number" payload_name = "number"
if "number" in payload: if "number" in channel_payload:
channel = payload["number"] channel = channel_payload["number"]
payload_name = "number" payload_name = "number"
elif "callSign" in payload: elif "callSign" in channel_payload:
channel = payload["callSign"] channel = channel_payload["callSign"]
payload_name = "callSign" payload_name = "callSign"
elif "affiliateCallSign" in payload: elif "affiliateCallSign" in channel_payload:
channel = payload["affiliateCallSign"] channel = channel_payload["affiliateCallSign"]
payload_name = "affiliateCallSign" payload_name = "affiliateCallSign"
elif "uri" in payload: elif "uri" in channel_payload:
channel = payload["uri"] channel = channel_payload["uri"]
payload_name = "uri" payload_name = "uri"
elif "name" in metadata_payload:
channel = metadata_payload["name"]
payload_name = "callSign"
data = { data = {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
@@ -1229,3 +1352,43 @@ async def async_api_seek(hass, config, directive, context):
return directive.response( return directive.response(
name="StateReport", namespace="Alexa.SeekController", payload=payload name="StateReport", namespace="Alexa.SeekController", payload=payload
) )
@HANDLERS.register(("Alexa.EqualizerController", "SetMode"))
async def async_api_set_eq_mode(hass, config, directive, context):
"""Process a SetMode request for EqualizerController."""
mode = directive.payload["mode"]
entity = directive.entity
data = {ATTR_ENTITY_ID: entity.entity_id}
sound_mode_list = entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
if sound_mode_list and mode.lower() in sound_mode_list:
data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
else:
msg = "failed to map sound mode {} to a mode on {}".format(
mode, entity.entity_id
)
raise AlexaInvalidValueError(msg)
await hass.services.async_call(
entity.domain,
media_player.SERVICE_SELECT_SOUND_MODE,
data,
blocking=False,
context=context,
)
return directive.response()
@HANDLERS.register(("Alexa.EqualizerController", "AdjustBands"))
@HANDLERS.register(("Alexa.EqualizerController", "ResetBands"))
@HANDLERS.register(("Alexa.EqualizerController", "SetBands"))
async def async_api_bands_directive(hass, config, directive, context):
"""Handle an AdjustBands, ResetBands, SetBands request.
Only mode directives are currently supported for the EqualizerController.
"""
# Currently bands directives are not supported.
msg = "Entity does not support directive"
raise AlexaInvalidDirectiveError(msg)

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 import homeassistant.core as ha
from .const import API_DIRECTIVE, API_HEADER from .const import API_DIRECTIVE, API_HEADER
from .errors import AlexaError, AlexaBridgeUnreachableError from .errors import AlexaBridgeUnreachableError, AlexaError
from .handlers import HANDLERS from .handlers import HANDLERS
from .messages import AlexaDirective from .messages import AlexaDirective

View File

@@ -13,8 +13,8 @@ from .const import (
CONF_ENTITY_CONFIG, CONF_ENTITY_CONFIG,
CONF_FILTER, CONF_FILTER,
) )
from .state_report import async_enable_proactive_mode
from .smart_home import async_handle_message from .smart_home import async_handle_message
from .state_report import async_enable_proactive_mode
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home" SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home"

View File

@@ -6,8 +6,8 @@ import logging
import aiohttp import aiohttp
import async_timeout import async_timeout
import homeassistant.util.dt as dt_util
from homeassistant.const import MATCH_ALL, STATE_ON from homeassistant.const import MATCH_ALL, STATE_ON
import homeassistant.util.dt as dt_util
from .const import API_CHANGE, Cause from .const import API_CHANGE, Cause
from .entities import ENTITY_ADAPTERS from .entities import ENTITY_ADAPTERS

View File

@@ -2,7 +2,13 @@
"config": { "config": {
"abort": { "abort": {
"already_setup": "\u041c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u0438\u043d Almond \u0430\u043a\u0430\u0443\u043d\u0442.", "already_setup": "\u041c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u0438\u043d Almond \u0430\u043a\u0430\u0443\u043d\u0442.",
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Almond \u0441\u044a\u0440\u0432\u044a\u0440\u0430." "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Almond \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
"missing_configuration": "\u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430 \u043a\u0430\u043a \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Almond."
},
"step": {
"pick_implementation": {
"title": "\u0418\u0437\u0431\u043e\u0440 \u043d\u0430 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u044f"
}
}, },
"title": "Almond" "title": "Almond"
} }

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