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

View File

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

View File

@@ -18,14 +18,31 @@ repos:
- --safe
- --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://gitlab.com/pycqa/flake8
- repo: https://github.com/PyCQA/flake8
rev: 3.7.9
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.5.0
- pydocstyle==4.0.1
- pydocstyle==5.0.1
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.6.2
hooks:
- id: bandit
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
# results depend on what is installed. And the mypy hook runs in a
# virtualenv of its own, meaning we'd need to install and maintain

View File

@@ -20,5 +20,22 @@ repos:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.5.0
- pydocstyle==4.0.1
- pydocstyle==5.0.1
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.6.2
hooks:
- id: bandit
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
python:
version: 3.6
version: 3.7
setup_py_install: true
requirements_file: requirements_docs.txt

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
"""Auth utils."""
import binascii
import os
def generate_secret(entropy: int = 32) -> str:
"""Generate a secret.
Backport of secrets.token_hex from Python 3.6
Event loop friendly.
"""
return binascii.hexlify(os.urandom(entropy)).decode("ascii")

View File

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

View File

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

View File

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

View File

@@ -5,8 +5,8 @@
},
"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.",
"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.",
"invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435."
"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\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435."
},
"step": {
"user": {

View File

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

View File

@@ -2,6 +2,10 @@
import logging
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 (
ATTR_ATTRIBUTION,
STATE_ALARM_ARMED_AWAY,
@@ -51,6 +55,11 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
state = None
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):
"""Send disarm command."""
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.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"

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo Hass.io AdGuard Home.",
"adguard_home_addon_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}. Aggiorna il componente aggiuntivo AdGuard Home di Hass.io.",
"adguard_home_outdated": "Questa integrazione richiede AdGuard Home {minimal_version} o versione successiva, si dispone di {current_version}.",
"existing_instance_updated": "Configurazione esistente aggiornata.",
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
@@ -11,7 +11,7 @@
},
"step": {
"hassio_confirm": {
"description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon} ?",
"description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon}?",
"title": "AdGuard Home tramite il componente aggiuntivo di Hass.io"
},
"user": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
from datetime import timedelta
import logging
from pyaftership.tracker import Tracking
import voluptuous as vol
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.entity import Entity
from homeassistant.util import Throttle
from .const import DOMAIN
_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):
"""Set up the AfterShip sensor platform."""
from pyaftership.tracker import Tracking
apikey = config[CONF_API_KEY]
name = config[CONF_NAME]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",
"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_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",
"disarm": "Desactiva {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": {
"action_type": {
"arm_away": "Tilkobl {entity_name} ude",
"arm_home": "Tilkobl {entity_name} hjemme",
"arm_night": "Tilkobl {entity_name} nat",
"disarm": "Frakobl {entity_name}",
"trigger": "Udl\u00f8s {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} tilkoblet ude",
"armed_home": "{entity_name} tilkoblet hjemme",
"armed_night": "{entity_name} tilkoblet nat",
"disarmed": "{entity_name} frakoblet",
"triggered": "{entity_name} udl\u00f8st"
}
}
}

View File

@@ -6,6 +6,13 @@
"arm_night": "Arm {entity_name} night",
"disarm": "Disarm {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",
"disarm": "Desarmar {entity_name}",
"trigger": "Lanzar {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} armado fuera",
"armed_home": "{entity_name} armado en casa",
"armed_night": "{entity_name} armado modo noche",
"disarmed": "{entity_name} desarmado",
"triggered": "{entity_name} activado"
}
}
}

View File

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

View File

@@ -0,0 +1,18 @@
{
"device_automation": {
"action_type": {
"arm_away": "{entity_name} \u00e9les\u00edt\u00e9se t\u00e1voz\u00f3 m\u00f3dban",
"arm_home": "{entity_name} \u00e9les\u00edt\u00e9se otthon marad\u00f3 m\u00f3dban",
"arm_night": "{entity_name} \u00e9les\u00edt\u00e9se \u00e9jszakai m\u00f3dban",
"disarm": "{entity_name} hat\u00e1stalan\u00edt\u00e1sa",
"trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa"
},
"trigger_type": {
"armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve",
"armed_home": "{entity_name} otthon marad\u00f3 m\u00f3dban lett \u00e9les\u00edtve",
"armed_night": "{entity_name} \u00e9jszakai m\u00f3dban lett \u00e9les\u00edtve",
"disarmed": "{entity_name} hat\u00e1stalan\u00edtva lett",
"triggered": "{entity_name} riaszt\u00e1sba ker\u00fclt"
}
}
}

View File

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

View File

@@ -6,6 +6,13 @@
"arm_night": "{entity_name} fir Nuecht uschalten",
"disarm": "{entity_name} entsch\u00e4rfen",
"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",
"disarm": "Uitschakelen {entity_name}",
"trigger": "Trigger {entity_name}"
},
"trigger_type": {
"armed_away": "{entity_name} afwezig ingeschakeld",
"armed_home": "{entity_name} thuis ingeschakeld",
"armed_night": "{entity_name} nachtstand ingeschakeld",
"disarmed": "{entity_name} uitgeschakeld",
"triggered": "{entity_name} geactiveerd"
}
}
}

View File

@@ -6,6 +6,13 @@
"arm_night": "Aktiver {entity_name} natt",
"disarm": "Deaktiver {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}",
"disarm": "rozbr\u00f3j {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",
"disarm": "Desarmar {entity_name}",
"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}",
"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_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",
"disarm": "Razoro\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",
"disarm": "\u89e3\u9664 {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."""
from abc import abstractmethod
from datetime import timedelta
import logging
@@ -7,22 +8,30 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_CODE,
ATTR_CODE_FORMAT,
SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
)
from homeassistant.helpers.config_validation import ( # noqa: F401
ENTITY_SERVICE_SCHEMA,
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER,
)
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_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"
SCAN_INTERVAL = timedelta(seconds=30)
ATTR_CHANGED_BY = "changed_by"
@@ -32,9 +41,7 @@ ATTR_CODE_ARM_REQUIRED = "code_arm_required"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
ALARM_SERVICE_SCHEMA = ENTITY_SERVICE_SCHEMA.extend(
{vol.Optional(ATTR_CODE): cv.string}
)
ALARM_SERVICE_SCHEMA = make_entity_service_schema({vol.Optional(ATTR_CODE): cv.string})
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"
)
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(
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(
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(
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
ALARM_SERVICE_SCHEMA,
"async_alarm_arm_custom_bypass",
[SUPPORT_ALARM_ARM_CUSTOM_BYPASS],
)
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
@@ -79,7 +99,6 @@ async def async_unload_entry(hass, entry):
return await hass.data[DOMAIN].async_unload_entry(entry)
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
"""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)
@property
@abstractmethod
def supported_features(self) -> int:
"""Return the list of supported features."""
@property
def state_attributes(self):
"""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."""
from typing import Optional, List
from typing import List, Optional
import voluptuous as vol
from homeassistant.const import (
@@ -16,10 +17,17 @@ from homeassistant.const import (
SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER,
)
from homeassistant.core import HomeAssistant, Context
from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv
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"}
@@ -42,31 +50,42 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
if entry.domain != DOMAIN:
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
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_away",
}
)
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_home",
}
)
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_night",
}
)
if supported_features & SUPPORT_ALARM_ARM_AWAY:
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_away",
}
)
if supported_features & SUPPORT_ALARM_ARM_HOME:
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_home",
}
)
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "arm_night",
}
)
actions.append(
{
CONF_DEVICE_ID: device_id,
@@ -75,14 +94,15 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
CONF_TYPE: "disarm",
}
)
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "trigger",
}
)
if supported_features & SUPPORT_ALARM_TRIGGER:
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "trigger",
}
)
return actions

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",
"requirements": [],
"dependencies": [],
"codeowners": [
"@colinodell"
]
"codeowners": []
}

View File

@@ -59,85 +59,3 @@ alarm_trigger:
code:
description: An optional code to trigger the alarm control panel with.
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",
"disarm": "Disarm {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."""
from datetime import timedelta
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
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
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_HOST
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import dt as dt_util
from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
_LOGGER = logging.getLogger(__name__)
@@ -109,9 +112,6 @@ CONFIG_SCHEMA = vol.Schema(
def setup(hass, config):
"""Set up for the AlarmDecoder devices."""
from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import SocketDevice, SerialDevice, USBDevice
conf = config.get(DOMAIN)
restart = False
@@ -134,8 +134,6 @@ def setup(hass, config):
def open_connection(now=None):
"""Open a connection to AlarmDecoder."""
from alarmdecoder.util import NoDeviceError
nonlocal restart
try:
controller.open(baud)

View File

@@ -3,7 +3,15 @@ import logging
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 (
ATTR_CODE,
STATE_ALARM_ARMED_AWAY,
@@ -13,11 +21,11 @@ from homeassistant.const import (
)
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__)
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})
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
@@ -36,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.alarm_toggle_chime(code)
hass.services.register(
alarm.DOMAIN,
DOMAIN,
SERVICE_ALARM_TOGGLE_CHIME,
alarm_toggle_chime_handler,
schema=ALARM_TOGGLE_CHIME_SCHEMA,
@@ -48,14 +56,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device.alarm_keypress(keypress)
hass.services.register(
DOMAIN_ALARMDECODER,
DOMAIN,
SERVICE_ALARM_KEYPRESS,
alarm_keypress_handler,
schema=ALARM_KEYPRESS_SCHEMA,
)
class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
class AlarmDecoderAlarmPanel(AlarmControlPanel):
"""Representation of an AlarmDecoder-based alarm panel."""
def __init__(self):
@@ -115,13 +123,18 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more digits/characters."""
return alarm.FORMAT_NUMBER
return FORMAT_NUMBER
@property
def state(self):
"""Return the state of the device."""
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
def device_state_attributes(self):
"""Return the state attributes."""

View File

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

View File

@@ -7,3 +7,13 @@ alarm_keypress:
keypress:
description: 'String to send to the alarm panel.'
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
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 (
CONF_CODE,
CONF_NAME,
@@ -95,6 +99,11 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_AWAY
return None
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
@property
def device_state_attributes(self):
"""Return the state attributes."""

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,10 @@
"""Alexa capabilities."""
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 (
ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE,
@@ -18,23 +22,26 @@ from homeassistant.const import (
STATE_UNKNOWN,
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.dt as dt_util
from .const import (
Catalog,
API_TEMP_UNITS,
API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS,
DATE_FORMAT,
PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP,
Inputs,
)
from .errors import UnsupportedProperty
from .resources import (
AlexaCapabilityResource,
AlexaGlobalCatalog,
AlexaModeResource,
AlexaPresetResource,
AlexaSemantics,
)
_LOGGER = logging.getLogger(__name__)
@@ -105,12 +112,41 @@ class AlexaCapability:
@staticmethod
def capability_resources():
"""Applicable to ToggleController, RangeController, and ModeController interfaces."""
"""Return the capability object.
Applicable to ToggleController, RangeController, and ModeController interfaces.
"""
return []
@staticmethod
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 []
@staticmethod
@@ -122,6 +158,10 @@ class AlexaCapability:
"""Serialize according to the Discovery API."""
result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"}
instance = self.instance
if instance is not None:
result["instance"] = instance
properties_supported = self.properties_supported()
if properties_supported:
result["properties"] = {
@@ -130,22 +170,19 @@ class AlexaCapability:
"retrievable": self.properties_retrievable(),
}
# pylint: disable=assignment-from-none
proactively_reported = self.capability_proactively_reported()
if proactively_reported is not None:
result["proactivelyReported"] = proactively_reported
# pylint: disable=assignment-from-none
non_controllable = self.properties_non_controllable()
if non_controllable is not None:
result["properties"]["nonControllable"] = non_controllable
# pylint: disable=assignment-from-none
supports_deactivation = self.supports_deactivation()
if supports_deactivation is not None:
result["supportsDeactivation"] = supports_deactivation
capability_resources = self.serialize_capability_resources()
capability_resources = self.capability_resources()
if capability_resources:
result["capabilityResources"] = capability_resources
@@ -153,15 +190,23 @@ class AlexaCapability:
if configuration:
result["configuration"] = configuration
# pylint: disable=assignment-from-none
instance = self.instance
if instance is not None:
result["instance"] = instance
# The plural configurations object is different than the singular configuration object above.
configurations = self.configurations()
if configurations:
result["configurations"] = configurations
semantics = self.semantics()
if semantics:
result["semantics"] = semantics
supported_operations = self.supported_operations()
if supported_operations:
result["supportedOperations"] = supported_operations
inputs = self.inputs()
if inputs:
result["inputs"] = inputs
return result
def serialize_properties(self):
@@ -184,35 +229,19 @@ class AlexaCapability:
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
def serialize_friendly_names(resources):
"""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"},
}
)
Although endpoints implement this interface implicitly,
The API suggests you should explicitly include this interface.
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):
@@ -236,7 +265,7 @@ class AlexaEndpointHealth(AlexaCapability):
def properties_proactively_reported(self):
"""Return True if properties asynchronously reported."""
return False
return True
def properties_retrievable(self):
"""Return True if properties can be retrieved."""
@@ -529,6 +558,23 @@ class AlexaInputController(AlexaCapability):
"""Return the Alexa API name of this interface."""
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):
"""Implements Alexa.TemperatureSensor.
@@ -752,10 +798,11 @@ class AlexaThermostatController(AlexaCapability):
supported_modes.append(thermostat_mode)
preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES)
for mode in preset_modes:
thermostat_mode = API_THERMOSTAT_PRESETS.get(mode)
if thermostat_mode:
supported_modes.append(thermostat_mode)
if preset_modes:
for mode in preset_modes:
thermostat_mode = API_THERMOSTAT_PRESETS.get(mode)
if thermostat_mode:
supported_modes.append(thermostat_mode)
# Return False for supportsScheduling until supported with event listener in handler.
configuration = {"supportsScheduling": False}
@@ -862,6 +909,8 @@ class AlexaModeController(AlexaCapability):
def __init__(self, entity, instance, non_controllable=False):
"""Initialize the entity."""
super().__init__(entity, instance)
self._resource = None
self._semantics = None
self.properties_non_controllable = lambda: non_controllable
def name(self):
@@ -878,73 +927,102 @@ class AlexaModeController(AlexaCapability):
def properties_retrievable(self):
"""Return True if properties can be retrieved."""
return True
def get_property(self, name):
"""Read and return a property."""
if name != "mode":
raise UnsupportedProperty(name)
# Fan 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
def configuration(self):
"""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):
"""Return capabilityResources object."""
capability_resources = []
# Fan Direction Resource
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
capability_resources = [
{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_DIRECTION}
]
self._resource = AlexaModeResource(
[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 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 None
return mode_resources
def semantics(self):
"""Build and return semantics object."""
def serialize_mode_resources(self):
"""Return ModeResources, friendlyNames serialized for an API response."""
mode_resources = []
resources = self.mode_resources()
ordered = resources["ordered"]
for resource in resources["resources"]:
mode_value = resource["value"]
friendly_names = resource["friendly_names"]
result = {
"value": mode_value,
"modeResources": {
"friendlyNames": self.serialize_friendly_names(friendly_names)
},
}
mode_resources.append(result)
# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
self._semantics = AlexaSemantics()
self._semantics.add_action_to_directive(
[AlexaSemantics.ACTION_CLOSE, AlexaSemantics.ACTION_LOWER],
"SetMode",
{"mode": f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}"},
)
self._semantics.add_action_to_directive(
[AlexaSemantics.ACTION_OPEN, AlexaSemantics.ACTION_RAISE],
"SetMode",
{"mode": f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}"},
)
self._semantics.add_states_to_value(
[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):
@@ -956,6 +1034,8 @@ class AlexaRangeController(AlexaCapability):
def __init__(self, entity, instance, non_controllable=False):
"""Initialize the entity."""
super().__init__(entity, instance)
self._resource = None
self._semantics = None
self.properties_non_controllable = lambda: non_controllable
def name(self):
@@ -979,88 +1059,137 @@ class AlexaRangeController(AlexaCapability):
if name != "rangeValue":
raise UnsupportedProperty(name)
# Fan Speed
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
speed = self.entity.attributes.get(fan.ATTR_SPEED)
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
def configuration(self):
"""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):
"""Return capabilityResources object."""
capability_resources = []
# Fan Speed Resources
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_SPEED}":
return [{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_FANSPEED}]
return capability_resources
def preset_resources(self):
"""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 = AlexaPresetResource(
labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED],
min_value=1,
max_value=3,
precision=1,
)
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 {
"supportedRange": {
"minimumValue": resources["minimumValue"],
"maximumValue": resources["maximumValue"],
"precision": resources["precision"],
},
"presets": preset_resources,
}
# Cover Position Resources
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
self._resource = AlexaPresetResource(
["Position", AlexaGlobalCatalog.SETTING_OPENING],
min_value=0,
max_value=100,
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):
@@ -1072,6 +1201,8 @@ class AlexaToggleController(AlexaCapability):
def __init__(self, entity, instance, non_controllable=False):
"""Initialize the entity."""
super().__init__(entity, instance)
self._resource = None
self._semantics = None
self.properties_non_controllable = lambda: non_controllable
def name(self):
@@ -1095,6 +1226,7 @@ class AlexaToggleController(AlexaCapability):
if name != "toggleState":
raise UnsupportedProperty(name)
# Fan Oscillating
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
is_on = bool(self.entity.attributes.get(fan.ATTR_OSCILLATING))
return "ON" if is_on else "OFF"
@@ -1103,16 +1235,15 @@ class AlexaToggleController(AlexaCapability):
def capability_resources(self):
"""Return capabilityResources object."""
capability_resources = []
# Fan Oscillating Resource
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
capability_resources = [
{"type": Catalog.LABEL_ASSET, "value": Catalog.SETTING_OSCILLATE},
{"type": Catalog.LABEL_TEXT, "value": "Rotate"},
{"type": Catalog.LABEL_TEXT, "value": "Rotation"},
]
self._resource = AlexaCapabilityResource(
[AlexaGlobalCatalog.SETTING_OSCILLATE, "Rotate", "Rotation"]
)
return self._resource.serialize_capability_resources()
return capability_resources
return None
class AlexaChannelController(AlexaCapability):
@@ -1186,3 +1317,112 @@ class AlexaSeekController(AlexaCapability):
def name(self):
"""Return the Alexa API name of this interface."""
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."""
from collections import OrderedDict
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.climate import const as climate
from homeassistant.components import fan
from homeassistant.components.climate import const as climate
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
DOMAIN = "alexa"
@@ -117,158 +117,90 @@ class Cause:
VOICE_INTERACTION = "VOICE_INTERACTION"
class Catalog:
"""The Global Alexa catalog.
class Inputs:
"""Valid names for the InputController.
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.
https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#input
"""
LABEL_ASSET = "asset"
LABEL_TEXT = "text"
VALID_SOURCE_NAME_MAP = {
"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
DEVICENAME_SHOWER = "Alexa.DeviceName.Shower"
# Washer, Washing Machine
DEVICENAME_WASHER = "Alexa.DeviceName.Washer"
# Router, Internet Router, Network Router, Wifi Router, Net Router
DEVICENAME_ROUTER = "Alexa.DeviceName.Router"
# Fan, Blower
DEVICENAME_FAN = "Alexa.DeviceName.Fan"
# Air Purifier, Air Cleaner,Clean Air Machine
DEVICENAME_AIRPURIFIER = "Alexa.DeviceName.AirPurifier"
# Space Heater, Portable Heater
DEVICENAME_SPACEHEATER = "Alexa.DeviceName.SpaceHeater"
# Rain Head, Overhead shower, Rain Shower, Rain Spout, Rain Faucet
SHOWER_RAINHEAD = "Alexa.Shower.RainHead"
# Handheld Shower, Shower Wand, Hand Shower
SHOWER_HANDHELD = "Alexa.Shower.HandHeld"
# Water Temperature, Water Temp, Water Heat
SETTING_WATERTEMPERATURE = "Alexa.Setting.WaterTemperature"
# Temperature, Temp
SETTING_TEMPERATURE = "Alexa.Setting.Temperature"
# Wash Cycle, Wash Preset, Wash setting
SETTING_WASHCYCLE = "Alexa.Setting.WashCycle"
# 2.4G Guest Wi-Fi, 2.4G Guest Network, Guest Network 2.4G, 2G Guest Wifi
SETTING_2GGUESTWIFI = "Alexa.Setting.2GGuestWiFi"
# 5G Guest Wi-Fi, 5G Guest Network, Guest Network 5G, 5G Guest Wifi
SETTING_5GGUESTWIFI = "Alexa.Setting.5GGuestWiFi"
# Guest Wi-fi, Guest Network, Guest Net
SETTING_GUESTWIFI = "Alexa.Setting.GuestWiFi"
# Auto, Automatic, Automatic Mode, Auto Mode
SETTING_AUTO = "Alexa.Setting.Auto"
# #Night, Night Mode
SETTING_NIGHT = "Alexa.Setting.Night"
# Quiet, Quiet Mode, Noiseless, Silent
SETTING_QUIET = "Alexa.Setting.Quiet"
# Oscillate, Swivel, Oscillation, Spin, Back and forth
SETTING_OSCILLATE = "Alexa.Setting.Oscillate"
# Fan Speed, Airflow speed, Wind Speed, Air speed, Air velocity
SETTING_FANSPEED = "Alexa.Setting.FanSpeed"
# Preset, Setting
SETTING_PRESET = "Alexa.Setting.Preset"
# Mode
SETTING_MODE = "Alexa.Setting.Mode"
# Direction
SETTING_DIRECTION = "Alexa.Setting.Direction"
# Delicates, Delicate
VALUE_DELICATE = "Alexa.Value.Delicate"
# Quick Wash, Fast Wash, Wash Quickly, Speed Wash
VALUE_QUICKWASH = "Alexa.Value.QuickWash"
# Maximum, Max
VALUE_MAXIMUM = "Alexa.Value.Maximum"
# Minimum, Min
VALUE_MINIMUM = "Alexa.Value.Minimum"
# High
VALUE_HIGH = "Alexa.Value.High"
# Low
VALUE_LOW = "Alexa.Value.Low"
# Medium, Mid
VALUE_MEDIUM = "Alexa.Value.Medium"
class Unit:
"""Alexa Units of Measure.
https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#units-of-measure
"""
ANGLE_DEGREES = "Alexa.Unit.Angle.Degrees"
ANGLE_RADIANS = "Alexa.Unit.Angle.Radians"
DISTANCE_FEET = "Alexa.Unit.Distance.Feet"
DISTANCE_INCHES = "Alexa.Unit.Distance.Inches"
DISTANCE_KILOMETERS = "Alexa.Unit.Distance.Kilometers"
DISTANCE_METERS = "Alexa.Unit.Distance.Meters"
DISTANCE_MILES = "Alexa.Unit.Distance.Miles"
DISTANCE_YARDS = "Alexa.Unit.Distance.Yards"
MASS_GRAMS = "Alexa.Unit.Mass.Grams"
MASS_KILOGRAMS = "Alexa.Unit.Mass.Kilograms"
PERCENT = "Alexa.Unit.Percent"
TEMPERATURE_CELSIUS = "Alexa.Unit.Temperature.Celsius"
TEMPERATURE_DEGREES = "Alexa.Unit.Temperature.Degrees"
TEMPERATURE_FAHRENHEIT = "Alexa.Unit.Temperature.Fahrenheit"
TEMPERATURE_KELVIN = "Alexa.Unit.Temperature.Kelvin"
VOLUME_CUBICFEET = "Alexa.Unit.Volume.CubicFeet"
VOLUME_CUBICMETERS = "Alexa.Unit.Volume.CubicMeters"
VOLUME_GALLONS = "Alexa.Unit.Volume.Gallons"
VOLUME_LITERS = "Alexa.Unit.Volume.Liters"
VOLUME_PINTS = "Alexa.Unit.Volume.Pints"
VOLUME_QUARTS = "Alexa.Unit.Volume.Quarts"
WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces"
WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds"
VALID_SOUND_MODE_MAP = {
"movie": "MOVIE",
"music": "MUSIC",
"night": "NIGHT",
"sport": "SPORT",
"tv": "TV",
}

View File

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

View File

@@ -3,10 +3,10 @@ import copy
import logging
import uuid
import homeassistant.util.dt as dt_util
from homeassistant.components import http
from homeassistant.core import callback
from homeassistant.helpers import template
import homeassistant.util.dt as dt_util
from .const import (
ATTR_MAIN_TEXT,

View File

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

View File

@@ -0,0 +1,387 @@
"""Alexa Resources and Assets."""
class AlexaGlobalCatalog:
"""The Global Alexa catalog.
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog
You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units.
This catalog is localized into all the languages that Alexa supports.
You can reference the following catalog of pre-defined friendly names.
Each item in the following list is an asset identifier followed by its supported friendly names.
The first friendly name for each identifier is the one displayed in the Alexa mobile app.
"""
# Air Purifier, Air Cleaner,Clean Air Machine
DEVICE_NAME_AIR_PURIFIER = "Alexa.DeviceName.AirPurifier"
# Fan, Blower
DEVICE_NAME_FAN = "Alexa.DeviceName.Fan"
# Router, Internet Router, Network Router, Wifi Router, Net Router
DEVICE_NAME_ROUTER = "Alexa.DeviceName.Router"
# Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, Window shade, Interior blind
DEVICE_NAME_SHADE = "Alexa.DeviceName.Shade"
# Shower
DEVICE_NAME_SHOWER = "Alexa.DeviceName.Shower"
# Space Heater, Portable Heater
DEVICE_NAME_SPACE_HEATER = "Alexa.DeviceName.SpaceHeater"
# Washer, Washing Machine
DEVICE_NAME_WASHER = "Alexa.DeviceName.Washer"
# 2.4G Guest Wi-Fi, 2.4G Guest Network, Guest Network 2.4G, 2G Guest Wifi
SETTING_2G_GUEST_WIFI = "Alexa.Setting.2GGuestWiFi"
# 5G Guest Wi-Fi, 5G Guest Network, Guest Network 5G, 5G Guest Wifi
SETTING_5G_GUEST_WIFI = "Alexa.Setting.5GGuestWiFi"
# Auto, Automatic, Automatic Mode, Auto Mode
SETTING_AUTO = "Alexa.Setting.Auto"
# Direction
SETTING_DIRECTION = "Alexa.Setting.Direction"
# Dry Cycle, Dry Preset, Dry Setting, Dryer Cycle, Dryer Preset, Dryer Setting
SETTING_DRY_CYCLE = "Alexa.Setting.DryCycle"
# Fan Speed, Airflow speed, Wind Speed, Air speed, Air velocity
SETTING_FAN_SPEED = "Alexa.Setting.FanSpeed"
# Guest Wi-fi, Guest Network, Guest Net
SETTING_GUEST_WIFI = "Alexa.Setting.GuestWiFi"
# Heat
SETTING_HEAT = "Alexa.Setting.Heat"
# Mode
SETTING_MODE = "Alexa.Setting.Mode"
# Night, Night Mode
SETTING_NIGHT = "Alexa.Setting.Night"
# Opening, Height, Lift, Width
SETTING_OPENING = "Alexa.Setting.Opening"
# Oscillate, Swivel, Oscillation, Spin, Back and forth
SETTING_OSCILLATE = "Alexa.Setting.Oscillate"
# Preset, Setting
SETTING_PRESET = "Alexa.Setting.Preset"
# Quiet, Quiet Mode, Noiseless, Silent
SETTING_QUIET = "Alexa.Setting.Quiet"
# Temperature, Temp
SETTING_TEMPERATURE = "Alexa.Setting.Temperature"
# Wash Cycle, Wash Preset, Wash setting
SETTING_WASH_CYCLE = "Alexa.Setting.WashCycle"
# Water Temperature, Water Temp, Water Heat
SETTING_WATER_TEMPERATURE = "Alexa.Setting.WaterTemperature"
# Handheld Shower, Shower Wand, Hand Shower
SHOWER_HAND_HELD = "Alexa.Shower.HandHeld"
# Rain Head, Overhead shower, Rain Shower, Rain Spout, Rain Faucet
SHOWER_RAIN_HEAD = "Alexa.Shower.RainHead"
# Degrees, Degree
UNIT_ANGLE_DEGREES = "Alexa.Unit.Angle.Degrees"
# Radians, Radian
UNIT_ANGLE_RADIANS = "Alexa.Unit.Angle.Radians"
# Feet, Foot
UNIT_DISTANCE_FEET = "Alexa.Unit.Distance.Feet"
# Inches, Inch
UNIT_DISTANCE_INCHES = "Alexa.Unit.Distance.Inches"
# Kilometers
UNIT_DISTANCE_KILOMETERS = "Alexa.Unit.Distance.Kilometers"
# Meters, Meter, m
UNIT_DISTANCE_METERS = "Alexa.Unit.Distance.Meters"
# Miles, Mile
UNIT_DISTANCE_MILES = "Alexa.Unit.Distance.Miles"
# Yards, Yard
UNIT_DISTANCE_YARDS = "Alexa.Unit.Distance.Yards"
# Grams, Gram, g
UNIT_MASS_GRAMS = "Alexa.Unit.Mass.Grams"
# Kilograms, Kilogram, kg
UNIT_MASS_KILOGRAMS = "Alexa.Unit.Mass.Kilograms"
# Percent
UNIT_PERCENT = "Alexa.Unit.Percent"
# Celsius, Degrees Celsius, Degrees, C, Centigrade, Degrees Centigrade
UNIT_TEMPERATURE_CELSIUS = "Alexa.Unit.Temperature.Celsius"
# Degrees, Degree
UNIT_TEMPERATURE_DEGREES = "Alexa.Unit.Temperature.Degrees"
# Fahrenheit, Degrees Fahrenheit, Degrees F, Degrees, F
UNIT_TEMPERATURE_FAHRENHEIT = "Alexa.Unit.Temperature.Fahrenheit"
# Kelvin, Degrees Kelvin, Degrees K, Degrees, K
UNIT_TEMPERATURE_KELVIN = "Alexa.Unit.Temperature.Kelvin"
# Cubic Feet, Cubic Foot
UNIT_VOLUME_CUBIC_FEET = "Alexa.Unit.Volume.CubicFeet"
# Cubic Meters, Cubic Meter, Meters Cubed
UNIT_VOLUME_CUBIC_METERS = "Alexa.Unit.Volume.CubicMeters"
# Gallons, Gallon
UNIT_VOLUME_GALLONS = "Alexa.Unit.Volume.Gallons"
# Liters, Liter, L
UNIT_VOLUME_LITERS = "Alexa.Unit.Volume.Liters"
# Pints, Pint
UNIT_VOLUME_PINTS = "Alexa.Unit.Volume.Pints"
# Quarts, Quart
UNIT_VOLUME_QUARTS = "Alexa.Unit.Volume.Quarts"
# Ounces, Ounce, oz
UNIT_WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces"
# Pounds, Pound, lbs
UNIT_WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds"
# Close
VALUE_CLOSE = "Alexa.Value.Close"
# Delicates, Delicate
VALUE_DELICATE = "Alexa.Value.Delicate"
# High
VALUE_HIGH = "Alexa.Value.High"
# Low
VALUE_LOW = "Alexa.Value.Low"
# Maximum, Max
VALUE_MAXIMUM = "Alexa.Value.Maximum"
# Medium, Mid
VALUE_MEDIUM = "Alexa.Value.Medium"
# Minimum, Min
VALUE_MINIMUM = "Alexa.Value.Minimum"
# Open
VALUE_OPEN = "Alexa.Value.Open"
# Quick Wash, Fast Wash, Wash Quickly, Speed Wash
VALUE_QUICK_WASH = "Alexa.Value.QuickWash"
class AlexaCapabilityResource:
"""Base class for Alexa capabilityResources, ModeResources, and presetResources objects.
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources
"""
def __init__(self, labels):
"""Initialize an Alexa resource."""
self._resource_labels = []
for label in labels:
self._resource_labels.append(label)
def serialize_capability_resources(self):
"""Return capabilityResources object serialized for an API response."""
return self.serialize_labels(self._resource_labels)
@staticmethod
def serialize_configuration():
"""Return ModeResources, PresetResources friendlyNames serialized for an API response."""
return []
@staticmethod
def serialize_labels(resources):
"""Return resource label objects for friendlyNames serialized for an API response."""
labels = []
for label in resources:
if label in AlexaGlobalCatalog.__dict__.values():
label = {"@type": "asset", "value": {"assetId": label}}
else:
label = {"@type": "text", "value": {"text": label, "locale": "en-US"}}
labels.append(label)
return {"friendlyNames": labels}
class AlexaModeResource(AlexaCapabilityResource):
"""Implements Alexa ModeResources.
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources
"""
def __init__(self, labels, ordered=False):
"""Initialize an Alexa modeResource."""
super().__init__(labels)
self._supported_modes = []
self._mode_ordered = ordered
def add_mode(self, value, labels):
"""Add mode to the supportedModes object."""
self._supported_modes.append({"value": value, "labels": labels})
def serialize_configuration(self):
"""Return configuration for ModeResources friendlyNames serialized for an API response."""
mode_resources = []
for mode in self._supported_modes:
result = {
"value": mode["value"],
"modeResources": self.serialize_labels(mode["labels"]),
}
mode_resources.append(result)
return {"ordered": self._mode_ordered, "supportedModes": mode_resources}
class AlexaPresetResource(AlexaCapabilityResource):
"""Implements Alexa PresetResources.
Use presetResources with RangeController to provide a set of friendlyNames for each RangeController preset.
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
"""
def __init__(self, labels, min_value, max_value, precision, unit=None):
"""Initialize an Alexa presetResource."""
super().__init__(labels)
self._presets = []
self._minimum_value = min_value
self._maximum_value = max_value
self._precision = precision
self._unit_of_measure = None
if unit in AlexaGlobalCatalog.__dict__.values():
self._unit_of_measure = unit
def add_preset(self, value, labels):
"""Add preset to configuration presets array."""
self._presets.append({"value": value, "labels": labels})
def serialize_configuration(self):
"""Return configuration for PresetResources friendlyNames serialized for an API response."""
configuration = {
"supportedRange": {
"minimumValue": self._minimum_value,
"maximumValue": self._maximum_value,
"precision": self._precision,
}
}
if self._unit_of_measure:
configuration["unitOfMeasure"] = self._unit_of_measure
if self._presets:
preset_resources = []
for preset in self._presets:
preset_resources.append(
{
"rangeValue": preset["value"],
"presetResources": self.serialize_labels(preset["labels"]),
}
)
configuration["presets"] = preset_resources
return configuration
class AlexaSemantics:
"""Class for Alexa Semantics Object.
You can optionally enable additional utterances by using semantics. When you use semantics,
you manually map the phrases "open", "close", "raise", and "lower" to directives.
Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object
"""
MAPPINGS_ACTION = "actionMappings"
MAPPINGS_STATE = "stateMappings"
ACTIONS_TO_DIRECTIVE = "ActionsToDirective"
STATES_TO_VALUE = "StatesToValue"
STATES_TO_RANGE = "StatesToRange"
ACTION_CLOSE = "Alexa.Actions.Close"
ACTION_LOWER = "Alexa.Actions.Lower"
ACTION_OPEN = "Alexa.Actions.Open"
ACTION_RAISE = "Alexa.Actions.Raise"
STATES_OPEN = "Alexa.States.Open"
STATES_CLOSED = "Alexa.States.Closed"
DIRECTIVE_RANGE_SET_VALUE = "SetRangeValue"
DIRECTIVE_RANGE_ADJUST_VALUE = "AdjustRangeValue"
DIRECTIVE_TOGGLE_TURN_ON = "TurnOn"
DIRECTIVE_TOGGLE_TURN_OFF = "TurnOff"
DIRECTIVE_MODE_SET_MODE = "SetMode"
DIRECTIVE_MODE_ADJUST_MODE = "AdjustMode"
def __init__(self):
"""Initialize an Alexa modeResource."""
self._action_mappings = []
self._state_mappings = []
def _add_action_mapping(self, semantics):
"""Add action mapping between actions and interface directives."""
self._action_mappings.append(semantics)
def _add_state_mapping(self, semantics):
"""Add state mapping between states and interface directives."""
self._state_mappings.append(semantics)
def add_states_to_value(self, states, value):
"""Add StatesToValue stateMappings."""
self._add_state_mapping(
{"@type": self.STATES_TO_VALUE, "states": states, "value": value}
)
def add_states_to_range(self, states, min_value, max_value):
"""Add StatesToRange stateMappings."""
self._add_state_mapping(
{
"@type": self.STATES_TO_RANGE,
"states": states,
"range": {"minimumValue": min_value, "maximumValue": max_value},
}
)
def add_action_to_directive(self, actions, directive, payload):
"""Add ActionsToDirective actionMappings."""
self._add_action_mapping(
{
"@type": self.ACTIONS_TO_DIRECTIVE,
"actions": actions,
"directive": {"name": directive, "payload": payload},
}
)
def serialize_semantics(self):
"""Return semantics object serialized for an API response."""
semantics = {}
if self._action_mappings:
semantics[self.MAPPINGS_ACTION] = self._action_mappings
if self._state_mappings:
semantics[self.MAPPINGS_STATE] = self._state_mappings
return semantics

View File

@@ -4,7 +4,7 @@ import logging
import homeassistant.core as ha
from .const import API_DIRECTIVE, API_HEADER
from .errors import AlexaError, AlexaBridgeUnreachableError
from .errors import AlexaBridgeUnreachableError, AlexaError
from .handlers import HANDLERS
from .messages import AlexaDirective

View File

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

View File

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

View File

@@ -2,7 +2,13 @@
"config": {
"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.",
"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"
}

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