mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
commit
2d7208470e
39
.coveragerc
39
.coveragerc
@ -10,7 +10,15 @@ omit =
|
||||
homeassistant/util/async.py
|
||||
|
||||
# omit pieces of code that rely on external devices being present
|
||||
homeassistant/components/abode/*
|
||||
homeassistant/components/abode/__init__.py
|
||||
homeassistant/components/abode/alarm_control_panel.py
|
||||
homeassistant/components/abode/binary_sensor.py
|
||||
homeassistant/components/abode/camera.py
|
||||
homeassistant/components/abode/cover.py
|
||||
homeassistant/components/abode/light.py
|
||||
homeassistant/components/abode/lock.py
|
||||
homeassistant/components/abode/sensor.py
|
||||
homeassistant/components/abode/switch.py
|
||||
homeassistant/components/acer_projector/switch.py
|
||||
homeassistant/components/actiontec/device_tracker.py
|
||||
homeassistant/components/adguard/__init__.py
|
||||
@ -19,6 +27,10 @@ omit =
|
||||
homeassistant/components/adguard/switch.py
|
||||
homeassistant/components/ads/*
|
||||
homeassistant/components/aftership/sensor.py
|
||||
homeassistant/components/airly/__init__.py
|
||||
homeassistant/components/airly/air_quality.py
|
||||
homeassistant/components/airly/sensor.py
|
||||
homeassistant/components/airly/const.py
|
||||
homeassistant/components/airvisual/sensor.py
|
||||
homeassistant/components/aladdin_connect/cover.py
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py
|
||||
@ -88,6 +100,7 @@ omit =
|
||||
homeassistant/components/bt_home_hub_5/device_tracker.py
|
||||
homeassistant/components/bt_smarthub/device_tracker.py
|
||||
homeassistant/components/buienradar/sensor.py
|
||||
homeassistant/components/buienradar/util.py
|
||||
homeassistant/components/buienradar/weather.py
|
||||
homeassistant/components/caldav/calendar.py
|
||||
homeassistant/components/canary/alarm_control_panel.py
|
||||
@ -113,7 +126,9 @@ omit =
|
||||
homeassistant/components/comfoconnect/*
|
||||
homeassistant/components/concord232/alarm_control_panel.py
|
||||
homeassistant/components/concord232/binary_sensor.py
|
||||
homeassistant/components/coolmaster/__init__.py
|
||||
homeassistant/components/coolmaster/climate.py
|
||||
homeassistant/components/coolmaster/const.py
|
||||
homeassistant/components/cppm_tracker/device_tracker.py
|
||||
homeassistant/components/cpuspeed/sensor.py
|
||||
homeassistant/components/crimereports/sensor.py
|
||||
@ -221,6 +236,7 @@ omit =
|
||||
homeassistant/components/fortios/device_tracker.py
|
||||
homeassistant/components/fortigate/*
|
||||
homeassistant/components/foscam/camera.py
|
||||
homeassistant/components/foscam/const.py
|
||||
homeassistant/components/foursquare/*
|
||||
homeassistant/components/free_mobile/notify.py
|
||||
homeassistant/components/freebox/*
|
||||
@ -240,6 +256,7 @@ omit =
|
||||
homeassistant/components/github/sensor.py
|
||||
homeassistant/components/gitlab_ci/sensor.py
|
||||
homeassistant/components/gitter/sensor.py
|
||||
homeassistant/components/glances/__init__.py
|
||||
homeassistant/components/glances/sensor.py
|
||||
homeassistant/components/gntp/notify.py
|
||||
homeassistant/components/goalfeed/*
|
||||
@ -271,7 +288,6 @@ omit =
|
||||
homeassistant/components/heatmiser/climate.py
|
||||
homeassistant/components/hikvision/binary_sensor.py
|
||||
homeassistant/components/hikvisioncam/switch.py
|
||||
homeassistant/components/hipchat/notify.py
|
||||
homeassistant/components/hitron_coda/device_tracker.py
|
||||
homeassistant/components/hive/*
|
||||
homeassistant/components/hlk_sw16/*
|
||||
@ -279,7 +295,6 @@ omit =
|
||||
homeassistant/components/homematic/climate.py
|
||||
homeassistant/components/homematic/cover.py
|
||||
homeassistant/components/homematic/notify.py
|
||||
homeassistant/components/homematicip_cloud/*
|
||||
homeassistant/components/homeworks/*
|
||||
homeassistant/components/honeywell/climate.py
|
||||
homeassistant/components/hook/switch.py
|
||||
@ -405,6 +420,7 @@ omit =
|
||||
homeassistant/components/mpchc/media_player.py
|
||||
homeassistant/components/mpd/media_player.py
|
||||
homeassistant/components/mqtt_room/sensor.py
|
||||
homeassistant/components/msteams/notify.py
|
||||
homeassistant/components/mvglive/sensor.py
|
||||
homeassistant/components/mychevy/*
|
||||
homeassistant/components/mycroft/*
|
||||
@ -417,7 +433,10 @@ omit =
|
||||
homeassistant/components/n26/*
|
||||
homeassistant/components/nad/media_player.py
|
||||
homeassistant/components/nanoleaf/light.py
|
||||
homeassistant/components/neato/*
|
||||
homeassistant/components/neato/camera.py
|
||||
homeassistant/components/neato/sensor.py
|
||||
homeassistant/components/neato/switch.py
|
||||
homeassistant/components/neato/vacuum.py
|
||||
homeassistant/components/nederlandse_spoorwegen/sensor.py
|
||||
homeassistant/components/nello/lock.py
|
||||
homeassistant/components/nest/*
|
||||
@ -461,7 +480,10 @@ omit =
|
||||
homeassistant/components/openhome/media_player.py
|
||||
homeassistant/components/opensensemap/air_quality.py
|
||||
homeassistant/components/opensky/sensor.py
|
||||
homeassistant/components/opentherm_gw/*
|
||||
homeassistant/components/opentherm_gw/__init__.py
|
||||
homeassistant/components/opentherm_gw/binary_sensor.py
|
||||
homeassistant/components/opentherm_gw/climate.py
|
||||
homeassistant/components/opentherm_gw/sensor.py
|
||||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/openuv/binary_sensor.py
|
||||
homeassistant/components/openuv/sensor.py
|
||||
@ -469,6 +491,7 @@ omit =
|
||||
homeassistant/components/openweathermap/weather.py
|
||||
homeassistant/components/opple/light.py
|
||||
homeassistant/components/orangepi_gpio/*
|
||||
homeassistant/components/oru/*
|
||||
homeassistant/components/orvibo/switch.py
|
||||
homeassistant/components/osramlightify/light.py
|
||||
homeassistant/components/otp/sensor.py
|
||||
@ -491,6 +514,7 @@ omit =
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/sensor.py
|
||||
homeassistant/components/plex/server.py
|
||||
homeassistant/components/plex/websockets.py
|
||||
homeassistant/components/plugwise/*
|
||||
homeassistant/components/plum_lightpad/*
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
@ -586,6 +610,7 @@ omit =
|
||||
homeassistant/components/skybeacon/sensor.py
|
||||
homeassistant/components/skybell/*
|
||||
homeassistant/components/slack/notify.py
|
||||
homeassistant/components/sinch/*
|
||||
homeassistant/components/slide/*
|
||||
homeassistant/components/sma/sensor.py
|
||||
homeassistant/components/smappee/*
|
||||
@ -599,6 +624,7 @@ omit =
|
||||
homeassistant/components/solaredge/__init__.py
|
||||
homeassistant/components/solaredge/sensor.py
|
||||
homeassistant/components/solaredge_local/sensor.py
|
||||
homeassistant/components/solarlog/*
|
||||
homeassistant/components/solax/sensor.py
|
||||
homeassistant/components/soma/cover.py
|
||||
homeassistant/components/soma/__init__.py
|
||||
@ -618,7 +644,6 @@ omit =
|
||||
homeassistant/components/steam_online/sensor.py
|
||||
homeassistant/components/stiebel_eltron/*
|
||||
homeassistant/components/streamlabswater/*
|
||||
homeassistant/components/stride/notify.py
|
||||
homeassistant/components/suez_water/*
|
||||
homeassistant/components/supervisord/sensor.py
|
||||
homeassistant/components/swiss_hydrological_data/sensor.py
|
||||
@ -676,9 +701,9 @@ omit =
|
||||
homeassistant/components/tradfri/*
|
||||
homeassistant/components/tradfri/light.py
|
||||
homeassistant/components/tradfri/cover.py
|
||||
homeassistant/components/tradfri/base_class.py
|
||||
homeassistant/components/trafikverket_train/sensor.py
|
||||
homeassistant/components/trafikverket_weatherstation/sensor.py
|
||||
homeassistant/components/transmission/__init__.py
|
||||
homeassistant/components/transmission/sensor.py
|
||||
homeassistant/components/transmission/switch.py
|
||||
homeassistant/components/transmission/const.py
|
||||
|
@ -5,7 +5,7 @@
|
||||
"dockerFile": "../Dockerfile.dev",
|
||||
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||
"appPort": 8123,
|
||||
"runArgs": ["-e", "GIT_EDITOR=\"code --wait\""],
|
||||
"runArgs": ["-e", "GIT_EDITOR=code --wait"],
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -128,3 +128,6 @@ monkeytype.sqlite3
|
||||
|
||||
# This is left behind by Azure Restore Cache
|
||||
tmp_cache
|
||||
|
||||
# python-language-server / Rope
|
||||
.ropeproject
|
||||
|
@ -6,6 +6,7 @@ repos:
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.8
|
||||
hooks:
|
||||
@ -13,3 +14,18 @@ repos:
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.3.1
|
||||
- pydocstyle==4.0.0
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
# 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
|
||||
# another set of our dependencies there... no. Use the "system" one
|
||||
# and reuse the environment that is set up anyway already instead.
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: mypy
|
||||
name: mypy
|
||||
entry: mypy
|
||||
language: system
|
||||
types: [python]
|
||||
require_serial: true
|
||||
files: ^homeassistant/.+\.py$
|
||||
|
@ -19,7 +19,7 @@ matrix:
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=lint
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=pylint
|
||||
env: TOXENV=pylint PYLINT_ARGS=--jobs=0
|
||||
- python: "3.6.1"
|
||||
env: TOXENV=typing
|
||||
- python: "3.6.1"
|
||||
@ -27,7 +27,10 @@ matrix:
|
||||
- python: "3.7"
|
||||
env: TOXENV=py37
|
||||
|
||||
cache: pip
|
||||
cache:
|
||||
pip: true
|
||||
directories:
|
||||
- $HOME/.cache/pre-commit
|
||||
install: pip install -U tox
|
||||
language: python
|
||||
script: travis_wait 50 tox --develop
|
||||
|
20
CODEOWNERS
20
CODEOWNERS
@ -13,10 +13,12 @@ homeassistant/util/* @home-assistant/core
|
||||
homeassistant/scripts/check_config.py @kellerza
|
||||
|
||||
# Integrations
|
||||
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
|
||||
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
|
||||
homeassistant/components/alpha_vantage/* @fabaff
|
||||
homeassistant/components/amazon_polly/* @robbiet480
|
||||
homeassistant/components/ambiclimate/* @danielhiversen
|
||||
@ -24,6 +26,7 @@ homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/androidtv/* @JeffLIrion
|
||||
homeassistant/components/apache_kafka/* @bachya
|
||||
homeassistant/components/api/* @home-assistant/core
|
||||
homeassistant/components/apprise/* @caronc
|
||||
homeassistant/components/aprs/* @PhilRW
|
||||
homeassistant/components/arcam_fmj/* @elupus
|
||||
homeassistant/components/arduino/* @fabaff
|
||||
@ -49,7 +52,7 @@ homeassistant/components/broadlink/* @danielhiversen
|
||||
homeassistant/components/brunt/* @eavanvalkenburg
|
||||
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
||||
homeassistant/components/buienradar/* @mjj4791 @ties
|
||||
homeassistant/components/cert_expiry/* @cereal2nd
|
||||
homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren
|
||||
homeassistant/components/cisco_ios/* @fbradyirl
|
||||
homeassistant/components/cisco_mobility_express/* @fbradyirl
|
||||
homeassistant/components/cisco_webex_teams/* @fbradyirl
|
||||
@ -97,15 +100,17 @@ homeassistant/components/flock/* @fabaff
|
||||
homeassistant/components/flunearyou/* @bachya
|
||||
homeassistant/components/fortigate/* @kifeo
|
||||
homeassistant/components/fortios/* @kimfrellsen
|
||||
homeassistant/components/foscam/* @skgsergio
|
||||
homeassistant/components/foursquare/* @robbiet480
|
||||
homeassistant/components/freebox/* @snoof85
|
||||
homeassistant/components/fronius/* @nielstron
|
||||
homeassistant/components/frontend/* @home-assistant/frontend
|
||||
homeassistant/components/gearbest/* @HerrHofrat
|
||||
homeassistant/components/geniushub/* @zxdavb
|
||||
homeassistant/components/geo_rss_events/* @exxamalte
|
||||
homeassistant/components/geonetnz_quakes/* @exxamalte
|
||||
homeassistant/components/gitter/* @fabaff
|
||||
homeassistant/components/glances/* @fabaff
|
||||
homeassistant/components/glances/* @fabaff @engrbm87
|
||||
homeassistant/components/gntp/* @robbiet480
|
||||
homeassistant/components/google_assistant/* @home-assistant/cloud
|
||||
homeassistant/components/google_cloud/* @lufton
|
||||
@ -152,6 +157,7 @@ homeassistant/components/izone/* @Swamp-Ig
|
||||
homeassistant/components/jewish_calendar/* @tsvi
|
||||
homeassistant/components/kaiterra/* @Michsior14
|
||||
homeassistant/components/keba/* @dannerph
|
||||
homeassistant/components/keenetic_ndms2/* @foxel
|
||||
homeassistant/components/knx/* @Julius2342
|
||||
homeassistant/components/kodi/* @armills
|
||||
homeassistant/components/konnected/* @heythisisnate
|
||||
@ -165,7 +171,7 @@ homeassistant/components/liveboxplaytv/* @pschmitt
|
||||
homeassistant/components/logger/* @home-assistant/core
|
||||
homeassistant/components/logi_circle/* @evanjd
|
||||
homeassistant/components/lovelace/* @home-assistant/frontend
|
||||
homeassistant/components/luci/* @fbradyirl
|
||||
homeassistant/components/luci/* @fbradyirl @mzdrale
|
||||
homeassistant/components/luftdaten/* @fabaff
|
||||
homeassistant/components/mastodon/* @fabaff
|
||||
homeassistant/components/matrix/* @tinloaf
|
||||
@ -184,8 +190,10 @@ homeassistant/components/monoprice/* @etsinko
|
||||
homeassistant/components/moon/* @fabaff
|
||||
homeassistant/components/mpd/* @fabaff
|
||||
homeassistant/components/mqtt/* @home-assistant/core
|
||||
homeassistant/components/msteams/* @peroyvind
|
||||
homeassistant/components/mysensors/* @MartinHjelmare
|
||||
homeassistant/components/mystrom/* @fabaff
|
||||
homeassistant/components/neato/* @dshokouhi @Santobert
|
||||
homeassistant/components/nello/* @pschmitt
|
||||
homeassistant/components/ness_alarm/* @nickw444
|
||||
homeassistant/components/nest/* @awarecan
|
||||
@ -209,6 +217,7 @@ homeassistant/components/opentherm_gw/* @mvn23
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/openweathermap/* @fabaff
|
||||
homeassistant/components/orangepi_gpio/* @pascallj
|
||||
homeassistant/components/oru/* @bvlaicu
|
||||
homeassistant/components/owlet/* @oblogic7
|
||||
homeassistant/components/panel_custom/* @home-assistant/frontend
|
||||
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
||||
@ -249,6 +258,7 @@ homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/shodan/* @fabaff
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/sinch/* @bendikrb
|
||||
homeassistant/components/slide/* @ualex73
|
||||
homeassistant/components/sma/* @kellerza
|
||||
homeassistant/components/smarthab/* @outadoc
|
||||
@ -256,6 +266,7 @@ homeassistant/components/smartthings/* @andrewsayre
|
||||
homeassistant/components/smarty/* @z0mbieprocess
|
||||
homeassistant/components/smtp/* @fabaff
|
||||
homeassistant/components/solaredge_local/* @drobtravels @scheric
|
||||
homeassistant/components/solarlog/* @Ernst79
|
||||
homeassistant/components/solax/* @squishykid
|
||||
homeassistant/components/soma/* @ratsept
|
||||
homeassistant/components/somfy/* @tetienne
|
||||
@ -312,6 +323,7 @@ homeassistant/components/velux/* @Julius2342
|
||||
homeassistant/components/version/* @fabaff
|
||||
homeassistant/components/vesync/* @markperdue @webdjoe
|
||||
homeassistant/components/vicare/* @oischinger
|
||||
homeassistant/components/vivotek/* @HarlemSquirrel
|
||||
homeassistant/components/vizio/* @raman325
|
||||
homeassistant/components/vlc_telnet/* @rodripf
|
||||
homeassistant/components/waqi/* @andrey-git
|
||||
|
@ -45,9 +45,10 @@ stages:
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
pre-commit install-hooks
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
flake8 homeassistant tests script
|
||||
pre-commit run flake8 --all-files
|
||||
displayName: 'Run flake8'
|
||||
- job: 'Validate'
|
||||
pool:
|
||||
@ -83,9 +84,10 @@ stages:
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
pre-commit install-hooks
|
||||
- script: |
|
||||
. venv/bin/activate
|
||||
./script/check_format
|
||||
pre-commit run black --all-files
|
||||
displayName: 'Check Black formatting'
|
||||
|
||||
- stage: 'Tests'
|
||||
@ -112,7 +114,7 @@ stages:
|
||||
python -m venv venv
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -U pip setuptools pytest-azurepipelines -c homeassistant/package_constraints.txt
|
||||
pip install -U pip setuptools pytest-azurepipelines pytest-xdist -c homeassistant/package_constraints.txt
|
||||
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
|
||||
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
|
||||
# Find offending deps with `pipdeptree -r -p typing`
|
||||
@ -125,7 +127,7 @@ stages:
|
||||
set -e
|
||||
|
||||
. venv/bin/activate
|
||||
pytest --timeout=9 --durations=10 -qq -o console_output_style=count -p no:sugar tests
|
||||
pytest --timeout=9 --durations=10 -n 2 --dist loadfile -qq -o console_output_style=count -p no:sugar tests
|
||||
script/check_dirty
|
||||
displayName: 'Run pytest for python $(python.container)'
|
||||
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
|
||||
@ -133,7 +135,7 @@ stages:
|
||||
set -e
|
||||
|
||||
. venv/bin/activate
|
||||
pytest --timeout=9 --durations=10 --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||
pytest --timeout=9 --durations=10 -n 2 --dist loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||
codecov --token $(codecovToken)
|
||||
script/check_dirty
|
||||
displayName: 'Run pytest for python $(python.container) / coverage'
|
||||
@ -172,17 +174,16 @@ stages:
|
||||
vmImage: 'ubuntu-latest'
|
||||
container: $[ variables['PythonMain'] ]
|
||||
steps:
|
||||
- script: |
|
||||
python -m venv venv
|
||||
- template: templates/azp-step-cache.yaml@azure
|
||||
parameters:
|
||||
keyfile: 'requirements_test.txt | setup.py | homeassistant/package_constraints.txt'
|
||||
build: |
|
||||
python -m venv venv
|
||||
|
||||
. venv/bin/activate
|
||||
pip install -e .
|
||||
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
displayName: 'Setup Env'
|
||||
. venv/bin/activate
|
||||
pip install -e . -r requirements_test.txt -c homeassistant/package_constraints.txt
|
||||
pre-commit install-hooks
|
||||
- script: |
|
||||
TYPING_FILES=$(cat mypyrc)
|
||||
echo -e "Run mypy on: \n$TYPING_FILES"
|
||||
|
||||
. venv/bin/activate
|
||||
mypy $TYPING_FILES
|
||||
pre-commit run mypy --all-files
|
||||
displayName: 'Run mypy'
|
||||
|
@ -18,7 +18,7 @@ schedules:
|
||||
always: true
|
||||
variables:
|
||||
- name: versionWheels
|
||||
value: '1.3-3.7-alpine3.10'
|
||||
value: '1.4-3.7-alpine3.10'
|
||||
resources:
|
||||
repositories:
|
||||
- repository: azure
|
||||
|
@ -56,7 +56,7 @@ homeassistant.helpers.data_entry_flow module
|
||||
homeassistant.helpers.deprecation module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: homeassistant.helpers.depracation
|
||||
.. automodule:: homeassistant.helpers.deprecation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
@ -45,7 +45,7 @@ async def auth_manager_from_config(
|
||||
)
|
||||
)
|
||||
else:
|
||||
providers = ()
|
||||
providers = []
|
||||
# So returned auth providers are in same order as config
|
||||
provider_hash: _ProviderDict = OrderedDict()
|
||||
for provider in providers:
|
||||
@ -57,7 +57,7 @@ async def auth_manager_from_config(
|
||||
*(auth_mfa_module_from_config(hass, config) for config in module_configs)
|
||||
)
|
||||
else:
|
||||
modules = ()
|
||||
modules = []
|
||||
# So returned auth modules are in same order as config
|
||||
module_hash: _MfaModuleDict = OrderedDict()
|
||||
for module in modules:
|
||||
@ -86,18 +86,6 @@ class AuthManager:
|
||||
hass, self._async_create_login_flow, self._async_finish_login_flow
|
||||
)
|
||||
|
||||
@property
|
||||
def support_legacy(self) -> bool:
|
||||
"""
|
||||
Return if legacy_api_password auth providers are registered.
|
||||
|
||||
Should be removed when we removed legacy_api_password auth providers.
|
||||
"""
|
||||
for provider_type, _ in self._providers:
|
||||
if provider_type == "legacy_api_password":
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def auth_providers(self) -> List[AuthProvider]:
|
||||
"""Return a list of available auth providers."""
|
||||
|
@ -64,13 +64,9 @@ async def async_from_config_dict(
|
||||
)
|
||||
|
||||
core_config = config.get(core.DOMAIN, {})
|
||||
api_password = config.get("http", {}).get("api_password")
|
||||
trusted_networks = config.get("http", {}).get("trusted_networks")
|
||||
|
||||
try:
|
||||
await conf_util.async_process_ha_core_config(
|
||||
hass, core_config, api_password, trusted_networks
|
||||
)
|
||||
await conf_util.async_process_ha_core_config(hass, core_config)
|
||||
except vol.Invalid as config_err:
|
||||
conf_util.async_log_exception(config_err, "homeassistant", core_config, hass)
|
||||
return None
|
||||
@ -97,11 +93,11 @@ async def async_from_config_dict(
|
||||
stop = time()
|
||||
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
|
||||
|
||||
if sys.version_info[:3] < (3, 6, 1):
|
||||
if sys.version_info[:3] < (3, 7, 0):
|
||||
msg = (
|
||||
"Python 3.6.0 support is deprecated and will "
|
||||
"be removed in the first release after October 2. Please "
|
||||
"upgrade Python to 3.6.1 or higher."
|
||||
"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."
|
||||
)
|
||||
_LOGGER.warning(msg)
|
||||
hass.components.persistent_notification.async_create(
|
||||
@ -264,7 +260,7 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
|
||||
domains = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN)
|
||||
|
||||
# Add config entry domains
|
||||
domains.update(hass.config_entries.async_domains()) # type: ignore
|
||||
domains.update(hass.config_entries.async_domains())
|
||||
|
||||
# Make sure the Hass.io component is loaded
|
||||
if "HASSIO" in os.environ:
|
||||
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"name_exists": "Name existiert bereits"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"latitude": "Breitengrad",
|
||||
"longitude": "L\u00e4ngengrad",
|
||||
"name": "Name der Integration"
|
||||
},
|
||||
"title": "Airly"
|
||||
}
|
||||
},
|
||||
"title": "Airly"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/ca.json
Normal file
22
homeassistant/components/abode/.translations/ca.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 d'Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "No es pot connectar amb Abode.",
|
||||
"identifier_exists": "Compte ja registrat.",
|
||||
"invalid_credentials": "Credencials inv\u00e0lides."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Contrasenya",
|
||||
"username": "Correu electr\u00f2nic"
|
||||
},
|
||||
"title": "Introdueix la teva informaci\u00f3 d'inici de sessi\u00f3 a Abode."
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/da.json
Normal file
22
homeassistant/components/abode/.translations/da.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Kunne ikke oprette forbindelse til Abode.",
|
||||
"identifier_exists": "Konto er allerede registreret.",
|
||||
"invalid_credentials": "Ugyldige legitimationsoplysninger."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Adgangskode",
|
||||
"username": "Email adresse"
|
||||
},
|
||||
"title": "Udfyld dine Abode-loginoplysninger"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/de.json
Normal file
22
homeassistant/components/abode/.translations/de.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Es ist nur eine einzige Konfiguration von Abode erlaubt."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Es kann keine Verbindung zu Abode hergestellt werden.",
|
||||
"identifier_exists": "Das Konto ist bereits registriert.",
|
||||
"invalid_credentials": "Ung\u00fcltige Anmeldeinformationen"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Passwort",
|
||||
"username": "E-Mail-Adresse"
|
||||
},
|
||||
"title": "Gib deine Abode-Anmeldeinformationen ein"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/en.json
Normal file
22
homeassistant/components/abode/.translations/en.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Only a single configuration of Abode is allowed."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Unable to connect to Abode.",
|
||||
"identifier_exists": "Account already registered.",
|
||||
"invalid_credentials": "Invalid credentials."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "Email Address"
|
||||
},
|
||||
"title": "Fill in your Abode login information"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/es.json
Normal file
22
homeassistant/components/abode/.translations/es.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Solo se permite una \u00fanica configuraci\u00f3n de Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "No se puede conectar a Abode.",
|
||||
"identifier_exists": "Cuenta ya registrada.",
|
||||
"invalid_credentials": "Credenciales inv\u00e1lidas."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Contrase\u00f1a",
|
||||
"username": "Direcci\u00f3n de correo electr\u00f3nico"
|
||||
},
|
||||
"title": "Rellene la informaci\u00f3n de acceso Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/fr.json
Normal file
22
homeassistant/components/abode/.translations/fr.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Une seule configuration d'Abode est autoris\u00e9e."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Impossible de se connecter \u00e0 Abode.",
|
||||
"identifier_exists": "Compte d\u00e9j\u00e0 enregistr\u00e9.",
|
||||
"invalid_credentials": "Informations d'identification invalides."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Mot de passe",
|
||||
"username": "Adresse e-mail"
|
||||
},
|
||||
"title": "Remplissez vos informations de connexion Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/it.json
Normal file
22
homeassistant/components/abode/.translations/it.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "\u00c8 consentita una sola configurazione di Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Impossibile connettersi ad Abode.",
|
||||
"identifier_exists": "Account gi\u00e0 registrato",
|
||||
"invalid_credentials": "Credenziali non valide"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "Indirizzo email"
|
||||
},
|
||||
"title": "Inserisci le tue informazioni di accesso Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/ko.json
Normal file
22
homeassistant/components/abode/.translations/ko.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "\ud558\ub098\uc758 Abode \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Abode \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.",
|
||||
"identifier_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||
"invalid_credentials": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "\ube44\ubc00\ubc88\ud638",
|
||||
"username": "\uc774\uba54\uc77c \uc8fc\uc18c"
|
||||
},
|
||||
"title": "Abode \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/lb.json
Normal file
22
homeassistant/components/abode/.translations/lb.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun ZHA ass erlaabt."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Kann sech net mat Abode verbannen.",
|
||||
"identifier_exists": "Konto ass scho registr\u00e9iert",
|
||||
"invalid_credentials": "Ong\u00eblteg Login Informatioune"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Passwuert",
|
||||
"username": "E-Mail Adress"
|
||||
},
|
||||
"title": "F\u00ebllt \u00e4r Abode Login Informatiounen aus."
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/nl.json
Normal file
22
homeassistant/components/abode/.translations/nl.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Slechts een enkele configuratie van Abode is toegestaan."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Kan geen verbinding maken met Abode.",
|
||||
"identifier_exists": "Account is al geregistreerd.",
|
||||
"invalid_credentials": "Ongeldige inloggegevens."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Wachtwoord",
|
||||
"username": "E-mailadres"
|
||||
},
|
||||
"title": "Vul uw Abode-inloggegevens in"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
5
homeassistant/components/abode/.translations/nn.json
Normal file
5
homeassistant/components/abode/.translations/nn.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/no.json
Normal file
22
homeassistant/components/abode/.translations/no.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Bare en enkelt konfigurasjon av Abode er tillatt."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Kan ikke koble til Abode.",
|
||||
"identifier_exists": "Kontoen er allerede registrert.",
|
||||
"invalid_credentials": "Ugyldig brukerinformasjon"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Passord",
|
||||
"username": "E-postadresse"
|
||||
},
|
||||
"title": "Fyll ut innloggingsinformasjonen for Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/pl.json
Normal file
22
homeassistant/components/abode/.translations/pl.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z Abode.",
|
||||
"identifier_exists": "Konto zosta\u0142o ju\u017c zarejestrowane",
|
||||
"invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Has\u0142o",
|
||||
"username": "Adres e-mail"
|
||||
},
|
||||
"title": "Wprowad\u017a informacje logowania Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
12
homeassistant/components/abode/.translations/pt-BR.json
Normal file
12
homeassistant/components/abode/.translations/pt-BR.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "Endere\u00e7o de e-mail"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
15
homeassistant/components/abode/.translations/pt.json
Normal file
15
homeassistant/components/abode/.translations/pt.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Conta j\u00e1 registada"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "Endere\u00e7o de e-mail"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/ru.json
Normal file
22
homeassistant/components/abode/.translations/ru.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "\u041f\u0430\u0440\u043e\u043b\u044c",
|
||||
"username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b"
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/sl.json
Normal file
22
homeassistant/components/abode/.translations/sl.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Dovoljena je samo ena konfiguracija Abode."
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Ni mogo\u010de vzpostaviti povezave z Abode.",
|
||||
"identifier_exists": "Ra\u010dun je \u017ee registriran.",
|
||||
"invalid_credentials": "Neveljavne poverilnice."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Geslo",
|
||||
"username": "E-po\u0161tni naslov"
|
||||
},
|
||||
"title": "Izpolnite svoje podatke za prijavo v Abode"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
22
homeassistant/components/abode/.translations/zh-Hant.json
Normal file
22
homeassistant/components/abode/.translations/zh-Hant.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 Abode\u3002"
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "\u7121\u6cd5\u9023\u7dda\u81f3 Abode\u3002",
|
||||
"identifier_exists": "\u5e33\u865f\u5df2\u8a3b\u518a\u3002",
|
||||
"invalid_credentials": "\u6191\u8b49\u7121\u6548\u3002"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "\u5bc6\u78bc",
|
||||
"username": "\u96fb\u5b50\u90f5\u4ef6\u5730\u5740"
|
||||
},
|
||||
"title": "\u586b\u5beb Abode \u767b\u5165\u8cc7\u8a0a"
|
||||
}
|
||||
},
|
||||
"title": "Abode"
|
||||
}
|
||||
}
|
@ -1,45 +1,36 @@
|
||||
"""Support for Abode Home Security system."""
|
||||
import logging
|
||||
"""Support for the Abode Security System."""
|
||||
from asyncio import gather
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
import logging
|
||||
|
||||
from abodepy import Abode
|
||||
from abodepy.exceptions import AbodeException
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DATE,
|
||||
ATTR_TIME,
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_USERNAME,
|
||||
ATTR_TIME,
|
||||
CONF_PASSWORD,
|
||||
CONF_EXCLUDE,
|
||||
CONF_NAME,
|
||||
CONF_LIGHTS,
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import ATTRIBUTION, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
CONF_POLLING = "polling"
|
||||
|
||||
DOMAIN = "abode"
|
||||
DEFAULT_CACHEDB = "./abodepy_cache.pickle"
|
||||
|
||||
NOTIFICATION_ID = "abode_notification"
|
||||
NOTIFICATION_TITLE = "Abode Security Setup"
|
||||
|
||||
EVENT_ABODE_ALARM = "abode_alarm"
|
||||
EVENT_ABODE_ALARM_END = "abode_alarm_end"
|
||||
EVENT_ABODE_AUTOMATION = "abode_automation"
|
||||
EVENT_ABODE_FAULT = "abode_panel_fault"
|
||||
EVENT_ABODE_RESTORE = "abode_panel_restore"
|
||||
|
||||
SERVICE_SETTINGS = "change_setting"
|
||||
SERVICE_CAPTURE_IMAGE = "capture_image"
|
||||
SERVICE_TRIGGER = "trigger_quick_action"
|
||||
@ -53,6 +44,8 @@ ATTR_EVENT_TYPE = "event_type"
|
||||
ATTR_EVENT_UTC = "event_utc"
|
||||
ATTR_SETTING = "setting"
|
||||
ATTR_USER_NAME = "user_name"
|
||||
ATTR_APP_TYPE = "app_type"
|
||||
ATTR_EVENT_BY = "event_by"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
ABODE_DEVICE_ID_LIST_SCHEMA = vol.Schema([str])
|
||||
@ -63,10 +56,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_POLLING, default=False): cv.boolean,
|
||||
vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA,
|
||||
vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA,
|
||||
}
|
||||
)
|
||||
},
|
||||
@ -96,83 +86,86 @@ ABODE_PLATFORMS = [
|
||||
class AbodeSystem:
|
||||
"""Abode System class."""
|
||||
|
||||
def __init__(self, username, password, cache, name, polling, exclude, lights):
|
||||
def __init__(self, abode, polling):
|
||||
"""Initialize the system."""
|
||||
import abodepy
|
||||
|
||||
self.abode = abodepy.Abode(
|
||||
username,
|
||||
password,
|
||||
auto_login=True,
|
||||
get_devices=True,
|
||||
get_automations=True,
|
||||
cache_path=cache,
|
||||
)
|
||||
self.name = name
|
||||
self.abode = abode
|
||||
self.polling = polling
|
||||
self.exclude = exclude
|
||||
self.lights = lights
|
||||
self.devices = []
|
||||
|
||||
def is_excluded(self, device):
|
||||
"""Check if a device is configured to be excluded."""
|
||||
return device.device_id in self.exclude
|
||||
|
||||
def is_automation_excluded(self, automation):
|
||||
"""Check if an automation is configured to be excluded."""
|
||||
return automation.automation_id in self.exclude
|
||||
|
||||
def is_light(self, device):
|
||||
"""Check if a switch device is configured as a light."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
return device.generic_type == CONST.TYPE_LIGHT or (
|
||||
device.generic_type == CONST.TYPE_SWITCH and device.device_id in self.lights
|
||||
)
|
||||
self.logout_listener = None
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up Abode component."""
|
||||
from abodepy.exceptions import AbodeException
|
||||
async def async_setup(hass, config):
|
||||
"""Set up Abode integration."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
username = conf.get(CONF_USERNAME)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
name = conf.get(CONF_NAME)
|
||||
polling = conf.get(CONF_POLLING)
|
||||
exclude = conf.get(CONF_EXCLUDE)
|
||||
lights = conf.get(CONF_LIGHTS)
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=deepcopy(conf)
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up Abode integration from a config entry."""
|
||||
username = config_entry.data.get(CONF_USERNAME)
|
||||
password = config_entry.data.get(CONF_PASSWORD)
|
||||
polling = config_entry.data.get(CONF_POLLING)
|
||||
|
||||
try:
|
||||
cache = hass.config.path(DEFAULT_CACHEDB)
|
||||
hass.data[DOMAIN] = AbodeSystem(
|
||||
username, password, cache, name, polling, exclude, lights
|
||||
abode = await hass.async_add_executor_job(
|
||||
Abode, username, password, True, True, True, cache
|
||||
)
|
||||
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
||||
|
||||
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
||||
|
||||
hass.components.persistent_notification.create(
|
||||
"Error: {}<br />"
|
||||
"You will need to restart hass after fixing."
|
||||
"".format(ex),
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID,
|
||||
)
|
||||
return False
|
||||
|
||||
setup_hass_services(hass)
|
||||
setup_hass_events(hass)
|
||||
setup_abode_events(hass)
|
||||
for platform in ABODE_PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||
)
|
||||
|
||||
await setup_hass_events(hass)
|
||||
await hass.async_add_executor_job(setup_hass_services, hass)
|
||||
await hass.async_add_executor_job(setup_abode_events, hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload a config entry."""
|
||||
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER)
|
||||
|
||||
tasks = []
|
||||
|
||||
for platform in ABODE_PLATFORMS:
|
||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
||||
tasks.append(
|
||||
hass.config_entries.async_forward_entry_unload(config_entry, platform)
|
||||
)
|
||||
|
||||
await gather(*tasks)
|
||||
|
||||
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
|
||||
await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout)
|
||||
|
||||
hass.data[DOMAIN].logout_listener()
|
||||
hass.data.pop(DOMAIN)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setup_hass_services(hass):
|
||||
"""Home assistant services."""
|
||||
from abodepy.exceptions import AbodeException
|
||||
|
||||
def change_setting(call):
|
||||
"""Change an Abode system setting."""
|
||||
@ -223,13 +216,9 @@ def setup_hass_services(hass):
|
||||
)
|
||||
|
||||
|
||||
def setup_hass_events(hass):
|
||||
async def setup_hass_events(hass):
|
||||
"""Home Assistant start and stop callbacks."""
|
||||
|
||||
def startup(event):
|
||||
"""Listen for push events."""
|
||||
hass.data[DOMAIN].abode.events.start()
|
||||
|
||||
def logout(event):
|
||||
"""Logout of Abode."""
|
||||
if not hass.data[DOMAIN].polling:
|
||||
@ -239,14 +228,15 @@ def setup_hass_events(hass):
|
||||
_LOGGER.info("Logged out of Abode")
|
||||
|
||||
if not hass.data[DOMAIN].polling:
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, startup)
|
||||
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, logout)
|
||||
hass.data[DOMAIN].logout_listener = hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, logout
|
||||
)
|
||||
|
||||
|
||||
def setup_abode_events(hass):
|
||||
"""Event callbacks."""
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
|
||||
def event_callback(event, event_json):
|
||||
"""Handle an event callback from Abode."""
|
||||
@ -259,6 +249,8 @@ def setup_abode_events(hass):
|
||||
ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ""),
|
||||
ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ""),
|
||||
ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ""),
|
||||
ATTR_APP_TYPE: event_json.get(ATTR_APP_TYPE, ""),
|
||||
ATTR_EVENT_BY: event_json.get(ATTR_EVENT_BY, ""),
|
||||
ATTR_DATE: event_json.get(ATTR_DATE, ""),
|
||||
ATTR_TIME: event_json.get(ATTR_TIME, ""),
|
||||
}
|
||||
@ -271,6 +263,12 @@ def setup_abode_events(hass):
|
||||
TIMELINE.PANEL_FAULT_GROUP,
|
||||
TIMELINE.PANEL_RESTORE_GROUP,
|
||||
TIMELINE.AUTOMATION_GROUP,
|
||||
TIMELINE.DISARM_GROUP,
|
||||
TIMELINE.ARM_GROUP,
|
||||
TIMELINE.TEST_GROUP,
|
||||
TIMELINE.CAPTURE_GROUP,
|
||||
TIMELINE.DEVICE_GROUP,
|
||||
TIMELINE.AUTOMATION_EDIT_GROUP,
|
||||
]
|
||||
|
||||
for event in events:
|
||||
@ -283,30 +281,36 @@ class AbodeDevice(Entity):
|
||||
"""Representation of an Abode device."""
|
||||
|
||||
def __init__(self, data, device):
|
||||
"""Initialize a sensor for Abode device."""
|
||||
"""Initialize Abode device."""
|
||||
self._data = data
|
||||
self._device = device
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
"""Subscribe to device events."""
|
||||
self.hass.async_add_job(
|
||||
self._data.abode.events.add_device_callback,
|
||||
self._device.device_id,
|
||||
self._update_callback,
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Unsubscribe from device events."""
|
||||
self.hass.async_add_job(
|
||||
self._data.abode.events.remove_all_device_callbacks, self._device.device_id
|
||||
)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return self._data.polling
|
||||
|
||||
def update(self):
|
||||
"""Update automation state."""
|
||||
"""Update device and automation states."""
|
||||
self._device.refresh()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
"""Return the name of the device."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
@ -320,6 +324,21 @@ class AbodeDevice(Entity):
|
||||
"device_type": self._device.type,
|
||||
}
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID to use for this device."""
|
||||
return self._device.device_uuid
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device registry information for this entity."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device.device_id)},
|
||||
"manufacturer": "Abode",
|
||||
"name": self._device.name,
|
||||
"device_type": self._device.type,
|
||||
}
|
||||
|
||||
def _update_callback(self, device):
|
||||
"""Update the device state."""
|
||||
self.schedule_update_ha_state()
|
||||
@ -354,7 +373,7 @@ class AbodeAutomation(Entity):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
"""Return the name of the automation."""
|
||||
return self._automation.name
|
||||
|
||||
@property
|
||||
@ -368,6 +387,6 @@ class AbodeAutomation(Entity):
|
||||
}
|
||||
|
||||
def _update_callback(self, device):
|
||||
"""Update the device state."""
|
||||
"""Update the automation state."""
|
||||
self._automation.refresh()
|
||||
self.schedule_update_ha_state()
|
||||
|
@ -9,32 +9,31 @@ from homeassistant.const import (
|
||||
STATE_ALARM_DISARMED,
|
||||
)
|
||||
|
||||
from . import ATTRIBUTION, DOMAIN as ABODE_DOMAIN, AbodeDevice
|
||||
from . import AbodeDevice
|
||||
from .const import ATTRIBUTION, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ICON = "mdi:security"
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up an alarm control panel for an Abode device."""
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)]
|
||||
|
||||
data.devices.extend(alarm_devices)
|
||||
|
||||
add_entities(alarm_devices)
|
||||
async_add_entities(
|
||||
[AbodeAlarm(data, await hass.async_add_executor_job(data.abode.get_alarm))]
|
||||
)
|
||||
|
||||
|
||||
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
def __init__(self, data, device, name):
|
||||
"""Initialize the alarm control panel."""
|
||||
super().__init__(data, device)
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
@ -65,11 +64,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
||||
"""Send arm away command."""
|
||||
self._device.set_away()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the alarm."""
|
||||
return self._name or super().name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
|
@ -1,19 +1,25 @@
|
||||
"""Support for Abode Security System binary sensors."""
|
||||
import logging
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice
|
||||
from . import AbodeAutomation, AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
device_types = [
|
||||
CONST.TYPE_CONNECTIVITY,
|
||||
@ -24,25 +30,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=device_types):
|
||||
if data.is_excluded(device):
|
||||
continue
|
||||
|
||||
for device in data.abode.get_devices(generic_type=device_types):
|
||||
devices.append(AbodeBinarySensor(data, device))
|
||||
|
||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION):
|
||||
if data.is_automation_excluded(automation):
|
||||
continue
|
||||
|
||||
devices.append(
|
||||
AbodeQuickActionBinarySensor(
|
||||
data, automation, TIMELINE.AUTOMATION_EDIT_GROUP
|
||||
)
|
||||
)
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
||||
|
@ -2,35 +2,36 @@
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
import requests
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Abode camera devices."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a camera for an Abode device."""
|
||||
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA):
|
||||
if data.is_excluded(device):
|
||||
continue
|
||||
|
||||
devices.append(AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE))
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeCamera(AbodeDevice, Camera):
|
||||
|
79
homeassistant/components/abode/config_flow.py
Normal file
79
homeassistant/components/abode/config_flow.py
Normal file
@ -0,0 +1,79 @@
|
||||
"""Config flow for the Abode Security System component."""
|
||||
import logging
|
||||
|
||||
from abodepy import Abode
|
||||
from abodepy.exceptions import AbodeException
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import DOMAIN # pylint: disable=W0611
|
||||
|
||||
CONF_POLLING = "polling"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for Abode."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self.data_schema = {
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
if not user_input:
|
||||
return self._show_form()
|
||||
|
||||
username = user_input[CONF_USERNAME]
|
||||
password = user_input[CONF_PASSWORD]
|
||||
polling = user_input.get(CONF_POLLING, False)
|
||||
|
||||
try:
|
||||
await self.hass.async_add_executor_job(Abode, username, password, True)
|
||||
|
||||
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
||||
if ex.errcode == 400:
|
||||
return self._show_form({"base": "invalid_credentials"})
|
||||
return self._show_form({"base": "connection_error"})
|
||||
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_USERNAME],
|
||||
data={
|
||||
CONF_USERNAME: username,
|
||||
CONF_PASSWORD: password,
|
||||
CONF_POLLING: polling,
|
||||
},
|
||||
)
|
||||
|
||||
@callback
|
||||
def _show_form(self, errors=None):
|
||||
"""Show the form to the user."""
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(self.data_schema),
|
||||
errors=errors if errors else {},
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
if self._async_current_entries():
|
||||
_LOGGER.warning("Only one configuration of abode is allowed.")
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
return await self.async_step_user(import_config)
|
3
homeassistant/components/abode/const.py
Normal file
3
homeassistant/components/abode/const.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""Constants for the Abode Security System component."""
|
||||
DOMAIN = "abode"
|
||||
ATTRIBUTION = "Data provided by goabode.com"
|
@ -1,29 +1,31 @@
|
||||
"""Support for Abode Security System covers."""
|
||||
import logging
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
from homeassistant.components.cover import CoverDevice
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Abode cover devices."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode cover devices."""
|
||||
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER):
|
||||
if data.is_excluded(device):
|
||||
continue
|
||||
|
||||
devices.append(AbodeCover(data, device))
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeCover(AbodeDevice, CoverDevice):
|
||||
|
@ -2,6 +2,8 @@
|
||||
import logging
|
||||
from math import ceil
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
@ -16,31 +18,27 @@ from homeassistant.util.color import (
|
||||
color_temperature_mired_to_kelvin,
|
||||
)
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode light devices."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
device_types = [CONST.TYPE_LIGHT, CONST.TYPE_SWITCH]
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
|
||||
# Get all regular lights that are not excluded or switches marked as lights
|
||||
for device in data.abode.get_devices(generic_type=device_types):
|
||||
if data.is_excluded(device) or not data.is_light(device):
|
||||
continue
|
||||
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LIGHT):
|
||||
devices.append(AbodeLight(data, device))
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeLight(AbodeDevice, Light):
|
||||
|
@ -1,29 +1,31 @@
|
||||
"""Support for Abode Security System locks."""
|
||||
"""Support for the Abode Security System locks."""
|
||||
import logging
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
from homeassistant.components.lock import LockDevice
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Abode lock devices."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode lock devices."""
|
||||
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK):
|
||||
if data.is_excluded(device):
|
||||
continue
|
||||
|
||||
devices.append(AbodeLock(data, device))
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeLock(AbodeDevice, LockDevice):
|
||||
|
@ -1,10 +1,13 @@
|
||||
{
|
||||
"domain": "abode",
|
||||
"name": "Abode",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||
"requirements": [
|
||||
"abodepy==0.15.0"
|
||||
"abodepy==0.16.6"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
"codeowners": [
|
||||
"@shred86"
|
||||
]
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
"""Support for Abode Security System sensors."""
|
||||
import logging
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
)
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
||||
from . import AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -19,23 +22,22 @@ SENSOR_TYPES = {
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
|
||||
if data.is_excluded(device):
|
||||
continue
|
||||
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
devices.append(AbodeSensor(data, device, sensor_type))
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeSensor(AbodeDevice):
|
||||
|
22
homeassistant/components/abode/strings.json
Normal file
22
homeassistant/components/abode/strings.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Abode",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Fill in your Abode login information",
|
||||
"data": {
|
||||
"username": "Email Address",
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"identifier_exists": "Account already registered.",
|
||||
"invalid_credentials": "Invalid credentials.",
|
||||
"connection_error": "Unable to connect to Abode."
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "Only a single configuration of Abode is allowed."
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +1,37 @@
|
||||
"""Support for Abode Security System switches."""
|
||||
import logging
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
|
||||
from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice
|
||||
from . import AbodeAutomation, AbodeDevice
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Abode switch devices."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Platform uses config entry setup."""
|
||||
pass
|
||||
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up Abode switch devices."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
devices = []
|
||||
|
||||
# Get all regular switches that are not excluded or marked as lights
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SWITCH):
|
||||
if data.is_excluded(device) or data.is_light(device):
|
||||
continue
|
||||
|
||||
devices.append(AbodeSwitch(data, device))
|
||||
|
||||
# Get all Abode automations that can be enabled/disabled
|
||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION):
|
||||
if data.is_automation_excluded(automation):
|
||||
continue
|
||||
|
||||
devices.append(
|
||||
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
|
||||
)
|
||||
|
||||
data.devices.extend(devices)
|
||||
|
||||
add_entities(devices)
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class AbodeSwitch(AbodeDevice, SwitchDevice):
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||
import logging
|
||||
import re
|
||||
import serial
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -73,7 +74,6 @@ class AcerSwitch(SwitchDevice):
|
||||
|
||||
def __init__(self, serial_port, name, timeout, write_timeout, **kwargs):
|
||||
"""Init of the Acer projector."""
|
||||
import serial
|
||||
|
||||
self.ser = serial.Serial(
|
||||
port=serial_port, timeout=timeout, write_timeout=write_timeout, **kwargs
|
||||
@ -90,7 +90,6 @@ class AcerSwitch(SwitchDevice):
|
||||
|
||||
def _write_read(self, msg):
|
||||
"""Write to the projector and read the return."""
|
||||
import serial
|
||||
|
||||
ret = ""
|
||||
# Sometimes the projector won't answer for no reason or the projector
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Aquesta integraci\u00f3 necessita la versi\u00f3 d'AdGuard Home {minimal_version} o una superior, tens la {current_version}. Actualitza el complement de Hass.io d'AdGuard Home.",
|
||||
"adguard_home_outdated": "Aquesta integraci\u00f3 necessita la versi\u00f3 d'AdGuard Home {minimal_version} o una superior, tens la {current_version}.",
|
||||
"existing_instance_updated": "S'ha actualitzat la configuraci\u00f3 existent.",
|
||||
"single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 d'AdGuard Home."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "This integration requires AdGuard Home {minimal_version} or higher, you have {current_version}. Please update your Hass.io AdGuard Home add-on.",
|
||||
"adguard_home_outdated": "This integration requires AdGuard Home {minimal_version} or higher, you have {current_version}.",
|
||||
"existing_instance_updated": "Updated existing configuration.",
|
||||
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Esta integraci\u00f3n requiere AdGuard Home {minimal_version} o superior, usted tiene {current_version}. Por favor, actualice su complemento Hass.io AdGuard Home.",
|
||||
"adguard_home_outdated": "Esta integraci\u00f3n requiere AdGuard Home {minimal_version} o superior, usted tiene {current_version}.",
|
||||
"existing_instance_updated": "Se ha actualizado la configuraci\u00f3n existente.",
|
||||
"single_instance_allowed": "S\u00f3lo se permite una \u00fanica configuraci\u00f3n de AdGuard Home."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Cette int\u00e9gration n\u00e9cessite AdGuard Home {minimal_version} ou une version ult\u00e9rieure, vous disposez de {current_version}. Veuillez mettre \u00e0 jour votre compl\u00e9ment Hass.io AdGuard Home.",
|
||||
"adguard_home_outdated": "Cette int\u00e9gration n\u00e9cessite AdGuard Home {minimal_version} ou une version ult\u00e9rieure, vous disposez de {current_version}.",
|
||||
"existing_instance_updated": "La configuration existante a \u00e9t\u00e9 mise \u00e0 jour.",
|
||||
"single_instance_allowed": "Une seule configuration d'AdGuard Home est autoris\u00e9e."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 AdGuard Home {minimal_version} \uc774\uc0c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ud604\uc7ac \ubc84\uc804\uc740 {current_version} \uc785\ub2c8\ub2e4. Hass.io AdGuard Home \uc560\ub4dc\uc628\uc744 \uc5c5\ub370\uc774\ud2b8 \ud574\uc8fc\uc138\uc694.",
|
||||
"adguard_home_outdated": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 AdGuard Home {minimal_version} \uc774\uc0c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ud604\uc7ac \ubc84\uc804\uc740 {current_version} \uc785\ub2c8\ub2e4.",
|
||||
"existing_instance_updated": "\uae30\uc874 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4.",
|
||||
"single_instance_allowed": "\ud558\ub098\uc758 AdGuard Home \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "D\u00ebs Integratioun ben\u00e9idegt AdgGuard Home {minimal_version} oder m\u00e9i, dir hutt {current_version}. Aktualis\u00e9iert w.e.g. \u00e4ren Hass.io AdGuard Home Add-on.",
|
||||
"adguard_home_outdated": "D\u00ebs Integratioun ben\u00e9idegt AdgGuard Home {minimal_version} oder m\u00e9i, dir hutt {current_version}.",
|
||||
"existing_instance_updated": "D\u00e9i bestehend Konfiguratioun ass ge\u00e4nnert.",
|
||||
"single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun AdGuard Home ass erlaabt."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Deze integratie vereist AdGuard Home {minimal_version} of hoger, u heeft {current_version}. Update uw Hass.io AdGuard Home-add-on.",
|
||||
"adguard_home_outdated": "Deze integratie vereist AdGuard Home {minimal_version} of hoger, u heeft {current_version}.",
|
||||
"existing_instance_updated": "Bestaande configuratie bijgewerkt.",
|
||||
"single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van AdGuard Home is toegestaan."
|
||||
},
|
||||
|
@ -6,6 +6,7 @@
|
||||
"username": "Brukarnamn"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "AdGuard Home"
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Denne integrasjonen krever AdGuard Home {minimal_version} eller h\u00f8yere, du har {current_version}. Vennligst oppdater Hass.io AdGuard Home-tillegget.",
|
||||
"adguard_home_outdated": "Denne integrasjonen krever AdGuard Home {minimal_version} eller h\u00f8yere, du har {current_version}.",
|
||||
"existing_instance_updated": "Oppdatert eksisterende konfigurasjon.",
|
||||
"single_instance_allowed": "Kun en konfigurasjon av AdGuard Hjemer tillatt."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Ta integracja wymaga AdGuard Home {minimal_version} lub nowszej wersji, masz {current_version}. Zaktualizuj sw\u00f3j dodatek Hass.io AdGuard Home.",
|
||||
"adguard_home_outdated": "Ta integracja wymaga AdGuard Home {minimal_version} lub nowszej wersji, masz {current_version}.",
|
||||
"existing_instance_updated": "Zaktualizowano istniej\u0105c\u0105 konfiguracj\u0119.",
|
||||
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
|
||||
},
|
||||
|
12
homeassistant/components/adguard/.translations/pt.json
Normal file
12
homeassistant/components/adguard/.translations/pt.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Servidor",
|
||||
"port": "Porta"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 AdGuard Home \u0432\u0435\u0440\u0441\u0438\u0438 {current_version}. \u0414\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u0441\u0438\u044f {minimal_version}, \u0438\u043b\u0438 \u0431\u043e\u043b\u0435\u0435 \u043d\u043e\u0432\u0430\u044f. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438, \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 Hass.io.",
|
||||
"adguard_home_outdated": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 AdGuard Home \u0432\u0435\u0440\u0441\u0438\u0438 {current_version}. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0432\u0435\u0440\u0441\u0438\u044e {minimal_version} \u0438\u043b\u0438 \u0431\u043e\u043b\u0435\u0435 \u043d\u043e\u0432\u0443\u044e.",
|
||||
"existing_instance_updated": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0430.",
|
||||
"single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "Za to integracijo je potrebna AdGuard Home {minimal_version} ali vi\u0161ja, vi imate {current_version}. Prosimo posodobite va\u0161 hass.io AdGuard Home dodatek.",
|
||||
"adguard_home_outdated": "Za to integracijo je potrebna AdGuard Home {minimal_version} ali vi\u0161ja, vi imate {current_version}.",
|
||||
"existing_instance_updated": "Posodobljena obstoje\u010da konfiguracija.",
|
||||
"single_instance_allowed": "Dovoljena je samo ena konfiguracija AdGuard Home."
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"adguard_home_addon_outdated": "\u6574\u5408\u9700\u8981 AdGuard Home {minimal_version} \u6216\u66f4\u65b0\u7248\u672c\uff0c\u60a8\u76ee\u524d\u4f7f\u7528\u7248\u672c\u70ba {current_version}\u3002\u8acb\u66f4\u65b0 Hass.io AdGuard Home \u5143\u4ef6\u3002",
|
||||
"adguard_home_outdated": "\u6574\u5408\u9700\u8981 AdGuard Home {minimal_version} \u6216\u66f4\u65b0\u7248\u672c\uff0c\u60a8\u76ee\u524d\u4f7f\u7528\u7248\u672c\u70ba {current_version}\u3002",
|
||||
"existing_instance_updated": "\u5df2\u66f4\u65b0\u73fe\u6709\u8a2d\u5b9a\u3002",
|
||||
"single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 AdGuard Home\u3002"
|
||||
},
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Support for AdGuard Home."""
|
||||
from distutils.version import LooseVersion
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
from adguardhome import AdGuardHome, AdGuardHomeError
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.adguard.const import (
|
||||
@ -10,6 +11,7 @@ from homeassistant.components.adguard.const import (
|
||||
DATA_ADGUARD_CLIENT,
|
||||
DATA_ADGUARD_VERION,
|
||||
DOMAIN,
|
||||
MIN_ADGUARD_HOME_VERSION,
|
||||
SERVICE_ADD_URL,
|
||||
SERVICE_DISABLE_URL,
|
||||
SERVICE_ENABLE_URL,
|
||||
@ -27,6 +29,7 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@ -64,6 +67,17 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[DATA_ADGUARD_CLIENT] = adguard
|
||||
|
||||
try:
|
||||
version = await adguard.version()
|
||||
except AdGuardHomeConnectionError as exception:
|
||||
raise ConfigEntryNotReady from exception
|
||||
|
||||
if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
_LOGGER.error(
|
||||
"This integration requires AdGuard Home v0.99.0 or higher to work correctly"
|
||||
)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
for component in "sensor", "switch":
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
|
@ -1,11 +1,12 @@
|
||||
"""Config flow to configure the AdGuard Home integration."""
|
||||
from distutils.version import LooseVersion
|
||||
import logging
|
||||
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.adguard.const import DOMAIN
|
||||
from homeassistant.components.adguard.const import DOMAIN, MIN_ADGUARD_HOME_VERSION
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@ -83,11 +84,20 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
)
|
||||
|
||||
try:
|
||||
await adguard.version()
|
||||
version = await adguard.version()
|
||||
except AdGuardHomeConnectionError:
|
||||
errors["base"] = "connection_error"
|
||||
return await self._show_setup_form(errors)
|
||||
|
||||
if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
return self.async_abort(
|
||||
reason="adguard_home_outdated",
|
||||
description_placeholders={
|
||||
"current_version": version,
|
||||
"minimal_version": MIN_ADGUARD_HOME_VERSION,
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_HOST],
|
||||
data={
|
||||
@ -156,11 +166,20 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
)
|
||||
|
||||
try:
|
||||
await adguard.version()
|
||||
version = await adguard.version()
|
||||
except AdGuardHomeConnectionError:
|
||||
errors["base"] = "connection_error"
|
||||
return await self._show_hassio_form(errors)
|
||||
|
||||
if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
return self.async_abort(
|
||||
reason="adguard_home_addon_outdated",
|
||||
description_placeholders={
|
||||
"current_version": version,
|
||||
"minimal_version": MIN_ADGUARD_HOME_VERSION,
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=self._hassio_discovery["addon"],
|
||||
data={
|
||||
|
@ -7,6 +7,8 @@ DATA_ADGUARD_VERION = "adguard_version"
|
||||
|
||||
CONF_FORCE = "force"
|
||||
|
||||
MIN_ADGUARD_HOME_VERSION = "v0.99.0"
|
||||
|
||||
SERVICE_ADD_URL = "add_url"
|
||||
SERVICE_DISABLE_URL = "disable_url"
|
||||
SERVICE_ENABLE_URL = "enable_url"
|
||||
|
@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/adguard",
|
||||
"requirements": [
|
||||
"adguardhome==0.2.1"
|
||||
"adguardhome==0.3.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
@ -23,8 +23,10 @@
|
||||
"connection_error": "Failed to connect."
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed.",
|
||||
"existing_instance_updated": "Updated existing configuration."
|
||||
"adguard_home_outdated": "This integration requires AdGuard Home {minimal_version} or higher, you have {current_version}.",
|
||||
"adguard_home_addon_outdated": "This integration requires AdGuard Home {minimal_version} or higher, you have {current_version}. Please update your Hass.io AdGuard Home add-on.",
|
||||
"existing_instance_updated": "Updated existing configuration.",
|
||||
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ from collections import namedtuple
|
||||
import asyncio
|
||||
import async_timeout
|
||||
|
||||
import pyads
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
@ -78,7 +80,6 @@ SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema(
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the ADS component."""
|
||||
import pyads
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
@ -161,7 +162,6 @@ class AdsHub:
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
"""Shutdown ADS connection."""
|
||||
import pyads
|
||||
|
||||
_LOGGER.debug("Shutting down ADS")
|
||||
for notification_item in self._notification_items.values():
|
||||
@ -187,7 +187,6 @@ class AdsHub:
|
||||
|
||||
def write_by_name(self, name, value, plc_datatype):
|
||||
"""Write a value to the device."""
|
||||
import pyads
|
||||
|
||||
with self._lock:
|
||||
try:
|
||||
@ -197,7 +196,6 @@ class AdsHub:
|
||||
|
||||
def read_by_name(self, name, plc_datatype):
|
||||
"""Read a value from the device."""
|
||||
import pyads
|
||||
|
||||
with self._lock:
|
||||
try:
|
||||
@ -207,7 +205,6 @@ class AdsHub:
|
||||
|
||||
def add_device_notification(self, name, plc_datatype, callback):
|
||||
"""Add a notification to the ADS devices."""
|
||||
import pyads
|
||||
|
||||
attr = pyads.NotificationAttrib(ctypes.sizeof(plc_datatype))
|
||||
|
||||
|
@ -146,10 +146,10 @@ class AfterShipSensor(Entity):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
UPDATE_TOPIC, self.force_update
|
||||
UPDATE_TOPIC, self._force_update
|
||||
)
|
||||
|
||||
async def force_update(self):
|
||||
async def _force_update(self):
|
||||
"""Force update of data."""
|
||||
await self.async_update(no_throttle=True)
|
||||
await self.async_update_ha_state()
|
||||
|
22
homeassistant/components/airly/.translations/de.json
Normal file
22
homeassistant/components/airly/.translations/de.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"auth": "Der API-Schl\u00fcssel ist nicht korrekt.",
|
||||
"name_exists": "Name existiert bereits",
|
||||
"wrong_location": "Keine Airly Luftmessstation an diesem Ort"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Airly API-Schl\u00fcssel",
|
||||
"latitude": "Breitengrad",
|
||||
"longitude": "L\u00e4ngengrad",
|
||||
"name": "Name der Integration"
|
||||
},
|
||||
"description": "Einrichtung der Airly-Luftqualit\u00e4t Integration. Um einen API-Schl\u00fcssel zu generieren, registriere dich auf https://developer.airly.eu/register",
|
||||
"title": "Airly"
|
||||
}
|
||||
},
|
||||
"title": "Airly"
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
"longitude": "Longitude",
|
||||
"name": "Nom de l'int\u00e9gration"
|
||||
},
|
||||
"description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air Airly. Pour g\u00e9n\u00e9rer une cl\u00e9 API, rendez-vous sur https://developer.airly.eu/register.",
|
||||
"title": "Airly"
|
||||
}
|
||||
},
|
22
homeassistant/components/airly/.translations/ko.json
Normal file
22
homeassistant/components/airly/.translations/ko.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"auth": "API \ud0a4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.",
|
||||
"name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4.",
|
||||
"wrong_location": "\uc774 \uc9c0\uc5ed\uc5d0\ub294 Airly \uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc774 \uc5c6\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Airly API \ud0a4",
|
||||
"latitude": "\uc704\ub3c4",
|
||||
"longitude": "\uacbd\ub3c4",
|
||||
"name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984"
|
||||
},
|
||||
"description": "Airly \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://developer.airly.eu/register \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694",
|
||||
"title": "Airly"
|
||||
}
|
||||
},
|
||||
"title": "Airly"
|
||||
}
|
||||
}
|
22
homeassistant/components/airly/.translations/nl.json
Normal file
22
homeassistant/components/airly/.translations/nl.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"auth": "API-sleutel is niet correct.",
|
||||
"name_exists": "Naam bestaat al.",
|
||||
"wrong_location": "Geen Airly meetstations in dit gebied."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Airly API-sleutel",
|
||||
"latitude": "Breedtegraad",
|
||||
"longitude": "Lengtegraad",
|
||||
"name": "Naam van de integratie"
|
||||
},
|
||||
"description": "Airly-integratie van luchtkwaliteit instellen. Ga naar https://developer.airly.eu/register om de API-sleutel te genereren",
|
||||
"title": "Airly"
|
||||
}
|
||||
},
|
||||
"title": "Airly"
|
||||
}
|
||||
}
|
14
homeassistant/components/airly/.translations/pt.json
Normal file
14
homeassistant/components/airly/.translations/pt.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude"
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
114
homeassistant/components/airly/__init__.py
Normal file
114
homeassistant/components/airly/__init__.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""The Airly component."""
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import async_timeout
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from airly import Airly
|
||||
from airly.exceptions import AirlyError
|
||||
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import Config, HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import (
|
||||
ATTR_API_ADVICE,
|
||||
ATTR_API_CAQI,
|
||||
ATTR_API_CAQI_DESCRIPTION,
|
||||
ATTR_API_CAQI_LEVEL,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
NO_AIRLY_SENSORS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: Config) -> bool:
|
||||
"""Set up configured Airly."""
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up Airly as config entry."""
|
||||
api_key = config_entry.data[CONF_API_KEY]
|
||||
latitude = config_entry.data[CONF_LATITUDE]
|
||||
longitude = config_entry.data[CONF_LONGITUDE]
|
||||
|
||||
websession = async_get_clientsession(hass)
|
||||
|
||||
airly = AirlyData(websession, api_key, latitude, longitude)
|
||||
|
||||
await airly.async_update()
|
||||
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airly
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, "air_quality")
|
||||
)
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload a config entry."""
|
||||
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||
await hass.config_entries.async_forward_entry_unload(config_entry, "air_quality")
|
||||
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
||||
return True
|
||||
|
||||
|
||||
class AirlyData:
|
||||
"""Define an object to hold Airly data."""
|
||||
|
||||
def __init__(self, session, api_key, latitude, longitude):
|
||||
"""Initialize."""
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.airly = Airly(api_key, session)
|
||||
self.data = {}
|
||||
|
||||
@Throttle(DEFAULT_SCAN_INTERVAL)
|
||||
async def async_update(self):
|
||||
"""Update Airly data."""
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(10):
|
||||
measurements = self.airly.create_measurements_session_point(
|
||||
self.latitude, self.longitude
|
||||
)
|
||||
await measurements.update()
|
||||
|
||||
values = measurements.current["values"]
|
||||
index = measurements.current["indexes"][0]
|
||||
standards = measurements.current["standards"]
|
||||
|
||||
if index["description"] == NO_AIRLY_SENSORS:
|
||||
_LOGGER.error("Can't retrieve data: no Airly sensors in this area")
|
||||
return
|
||||
for value in values:
|
||||
self.data[value["name"]] = value["value"]
|
||||
for standard in standards:
|
||||
self.data[f"{standard['pollutant']}_LIMIT"] = standard["limit"]
|
||||
self.data[f"{standard['pollutant']}_PERCENT"] = standard["percent"]
|
||||
self.data[ATTR_API_CAQI] = index["value"]
|
||||
self.data[ATTR_API_CAQI_LEVEL] = index["level"].lower().replace("_", " ")
|
||||
self.data[ATTR_API_CAQI_DESCRIPTION] = index["description"]
|
||||
self.data[ATTR_API_ADVICE] = index["advice"]
|
||||
_LOGGER.debug("Data retrieved from Airly")
|
||||
except (
|
||||
ValueError,
|
||||
AirlyError,
|
||||
asyncio.TimeoutError,
|
||||
ClientConnectorError,
|
||||
) as error:
|
||||
_LOGGER.error(error)
|
||||
self.data = {}
|
138
homeassistant/components/airly/air_quality.py
Normal file
138
homeassistant/components/airly/air_quality.py
Normal file
@ -0,0 +1,138 @@
|
||||
"""Support for the Airly air_quality service."""
|
||||
from homeassistant.components.air_quality import (
|
||||
AirQualityEntity,
|
||||
ATTR_AQI,
|
||||
ATTR_PM_10,
|
||||
ATTR_PM_2_5,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
|
||||
from .const import (
|
||||
ATTR_API_ADVICE,
|
||||
ATTR_API_CAQI,
|
||||
ATTR_API_CAQI_DESCRIPTION,
|
||||
ATTR_API_CAQI_LEVEL,
|
||||
ATTR_API_PM10,
|
||||
ATTR_API_PM10_LIMIT,
|
||||
ATTR_API_PM10_PERCENT,
|
||||
ATTR_API_PM25,
|
||||
ATTR_API_PM25_LIMIT,
|
||||
ATTR_API_PM25_PERCENT,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
ATTRIBUTION = "Data provided by Airly"
|
||||
|
||||
LABEL_ADVICE = "advice"
|
||||
LABEL_AQI_LEVEL = f"{ATTR_AQI}_level"
|
||||
LABEL_PM_2_5_LIMIT = f"{ATTR_PM_2_5}_limit"
|
||||
LABEL_PM_2_5_PERCENT = f"{ATTR_PM_2_5}_percent_of_limit"
|
||||
LABEL_PM_10_LIMIT = f"{ATTR_PM_10}_limit"
|
||||
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]
|
||||
|
||||
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||
|
||||
async_add_entities([AirlyAirQuality(data, name)], True)
|
||||
|
||||
|
||||
def round_state(func):
|
||||
"""Round state."""
|
||||
|
||||
def _decorator(self):
|
||||
res = func(self)
|
||||
if isinstance(res, float):
|
||||
return round(res)
|
||||
return res
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
class AirlyAirQuality(AirQualityEntity):
|
||||
"""Define an Airly air quality."""
|
||||
|
||||
def __init__(self, airly, name):
|
||||
"""Initialize."""
|
||||
self.airly = airly
|
||||
self.data = airly.data
|
||||
self._name = name
|
||||
self._pm_2_5 = None
|
||||
self._pm_10 = None
|
||||
self._aqi = None
|
||||
self._icon = "mdi:blur"
|
||||
self._attrs = {}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
@round_state
|
||||
def air_quality_index(self):
|
||||
"""Return the air quality index."""
|
||||
return self._aqi
|
||||
|
||||
@property
|
||||
@round_state
|
||||
def particulate_matter_2_5(self):
|
||||
"""Return the particulate matter 2.5 level."""
|
||||
return self._pm_2_5
|
||||
|
||||
@property
|
||||
@round_state
|
||||
def particulate_matter_10(self):
|
||||
"""Return the particulate matter 10 level."""
|
||||
return self._pm_10
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return ATTRIBUTION
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the CAQI description."""
|
||||
return self.data[ATTR_API_CAQI_DESCRIPTION]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return f"{self.airly.latitude}-{self.airly.longitude}"
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return bool(self.airly.data)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
self._attrs[LABEL_ADVICE] = self.data[ATTR_API_ADVICE]
|
||||
self._attrs[LABEL_AQI_LEVEL] = self.data[ATTR_API_CAQI_LEVEL]
|
||||
self._attrs[LABEL_PM_2_5_LIMIT] = self.data[ATTR_API_PM25_LIMIT]
|
||||
self._attrs[LABEL_PM_2_5_PERCENT] = round(self.data[ATTR_API_PM25_PERCENT])
|
||||
self._attrs[LABEL_PM_10_LIMIT] = self.data[ATTR_API_PM10_LIMIT]
|
||||
self._attrs[LABEL_PM_10_PERCENT] = round(self.data[ATTR_API_PM10_PERCENT])
|
||||
return self._attrs
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the entity."""
|
||||
await self.airly.async_update()
|
||||
|
||||
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]
|
114
homeassistant/components/airly/config_flow.py
Normal file
114
homeassistant/components/airly/config_flow.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""Adds config flow for Airly."""
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
from airly import Airly
|
||||
from airly.exceptions import AirlyError
|
||||
|
||||
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
|
||||
|
||||
from .const import DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
|
||||
|
||||
|
||||
@callback
|
||||
def configured_instances(hass):
|
||||
"""Return a set of configured Airly instances."""
|
||||
return set(
|
||||
entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN)
|
||||
)
|
||||
|
||||
|
||||
class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for Airly."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self._errors = {}
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
self._errors = {}
|
||||
|
||||
websession = async_get_clientsession(self.hass)
|
||||
|
||||
if user_input is not None:
|
||||
if user_input[CONF_NAME] in configured_instances(self.hass):
|
||||
self._errors[CONF_NAME] = "name_exists"
|
||||
api_key_valid = await self._test_api_key(websession, user_input["api_key"])
|
||||
if not api_key_valid:
|
||||
self._errors["base"] = "auth"
|
||||
else:
|
||||
location_valid = await self._test_location(
|
||||
websession,
|
||||
user_input["api_key"],
|
||||
user_input["latitude"],
|
||||
user_input["longitude"],
|
||||
)
|
||||
if not location_valid:
|
||||
self._errors["base"] = "wrong_location"
|
||||
|
||||
if not self._errors:
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_NAME], data=user_input
|
||||
)
|
||||
|
||||
return self._show_config_form(
|
||||
name=DEFAULT_NAME,
|
||||
api_key="",
|
||||
latitude=self.hass.config.latitude,
|
||||
longitude=self.hass.config.longitude,
|
||||
)
|
||||
|
||||
def _show_config_form(self, name=None, api_key=None, latitude=None, longitude=None):
|
||||
"""Show the configuration form to edit data."""
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_API_KEY, default=api_key): str,
|
||||
vol.Optional(
|
||||
CONF_LATITUDE, default=self.hass.config.latitude
|
||||
): cv.latitude,
|
||||
vol.Optional(
|
||||
CONF_LONGITUDE, default=self.hass.config.longitude
|
||||
): cv.longitude,
|
||||
vol.Optional(CONF_NAME, default=name): str,
|
||||
}
|
||||
),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def _test_api_key(self, client, api_key):
|
||||
"""Return true if api_key is valid."""
|
||||
|
||||
with async_timeout.timeout(10):
|
||||
airly = Airly(api_key, client)
|
||||
measurements = airly.create_measurements_session_point(
|
||||
latitude=52.24131, longitude=20.99101
|
||||
)
|
||||
try:
|
||||
await measurements.update()
|
||||
except AirlyError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def _test_location(self, client, api_key, latitude, longitude):
|
||||
"""Return true if location is valid."""
|
||||
|
||||
with async_timeout.timeout(10):
|
||||
airly = Airly(api_key, client)
|
||||
measurements = airly.create_measurements_session_point(
|
||||
latitude=latitude, longitude=longitude
|
||||
)
|
||||
|
||||
await measurements.update()
|
||||
current = measurements.current
|
||||
if current["indexes"][0]["description"] == NO_AIRLY_SENSORS:
|
||||
return False
|
||||
return True
|
19
homeassistant/components/airly/const.py
Normal file
19
homeassistant/components/airly/const.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""Constants for Airly integration."""
|
||||
ATTR_API_ADVICE = "ADVICE"
|
||||
ATTR_API_CAQI = "CAQI"
|
||||
ATTR_API_CAQI_DESCRIPTION = "DESCRIPTION"
|
||||
ATTR_API_CAQI_LEVEL = "LEVEL"
|
||||
ATTR_API_HUMIDITY = "HUMIDITY"
|
||||
ATTR_API_PM1 = "PM1"
|
||||
ATTR_API_PM10 = "PM10"
|
||||
ATTR_API_PM10_LIMIT = "PM10_LIMIT"
|
||||
ATTR_API_PM10_PERCENT = "PM10_PERCENT"
|
||||
ATTR_API_PM25 = "PM25"
|
||||
ATTR_API_PM25_LIMIT = "PM25_LIMIT"
|
||||
ATTR_API_PM25_PERCENT = "PM25_PERCENT"
|
||||
ATTR_API_PRESSURE = "PRESSURE"
|
||||
ATTR_API_TEMPERATURE = "TEMPERATURE"
|
||||
DATA_CLIENT = "client"
|
||||
DEFAULT_NAME = "Airly"
|
||||
DOMAIN = "airly"
|
||||
NO_AIRLY_SENSORS = "There are no Airly sensors in this area yet."
|
9
homeassistant/components/airly/manifest.json
Normal file
9
homeassistant/components/airly/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"domain": "airly",
|
||||
"name": "Airly",
|
||||
"documentation": "https://www.home-assistant.io/integrations/airly",
|
||||
"dependencies": [],
|
||||
"codeowners": ["@bieniu"],
|
||||
"requirements": ["airly==0.0.2"],
|
||||
"config_flow": true
|
||||
}
|
150
homeassistant/components/airly/sensor.py
Normal file
150
homeassistant/components/airly/sensor.py
Normal file
@ -0,0 +1,150 @@
|
||||
"""Support for the Airly sensor service."""
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DEVICE_CLASS,
|
||||
CONF_NAME,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
PRESSURE_HPA,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import (
|
||||
ATTR_API_HUMIDITY,
|
||||
ATTR_API_PM1,
|
||||
ATTR_API_PRESSURE,
|
||||
ATTR_API_TEMPERATURE,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
ATTRIBUTION = "Data provided by Airly"
|
||||
|
||||
ATTR_ICON = "icon"
|
||||
ATTR_LABEL = "label"
|
||||
ATTR_UNIT = "unit"
|
||||
|
||||
HUMI_PERCENT = "%"
|
||||
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m³"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
ATTR_API_PM1: {
|
||||
ATTR_DEVICE_CLASS: None,
|
||||
ATTR_ICON: "mdi:blur",
|
||||
ATTR_LABEL: ATTR_API_PM1,
|
||||
ATTR_UNIT: VOLUME_MICROGRAMS_PER_CUBIC_METER,
|
||||
},
|
||||
ATTR_API_HUMIDITY: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
|
||||
ATTR_UNIT: HUMI_PERCENT,
|
||||
},
|
||||
ATTR_API_PRESSURE: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: ATTR_API_PRESSURE.capitalize(),
|
||||
ATTR_UNIT: PRESSURE_HPA,
|
||||
},
|
||||
ATTR_API_TEMPERATURE: {
|
||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||
ATTR_ICON: None,
|
||||
ATTR_LABEL: ATTR_API_TEMPERATURE.capitalize(),
|
||||
ATTR_UNIT: TEMP_CELSIUS,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
|
||||
data = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||
|
||||
sensors = []
|
||||
for sensor in SENSOR_TYPES:
|
||||
sensors.append(AirlySensor(data, name, sensor))
|
||||
async_add_entities(sensors, True)
|
||||
|
||||
|
||||
def round_state(func):
|
||||
"""Round state."""
|
||||
|
||||
def _decorator(self):
|
||||
res = func(self)
|
||||
if isinstance(res, float):
|
||||
return round(res)
|
||||
return res
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
class AirlySensor(Entity):
|
||||
"""Define an Airly sensor."""
|
||||
|
||||
def __init__(self, airly, name, kind):
|
||||
"""Initialize."""
|
||||
self.airly = airly
|
||||
self.data = airly.data
|
||||
self._name = name
|
||||
self.kind = kind
|
||||
self._device_class = None
|
||||
self._state = None
|
||||
self._icon = None
|
||||
self._unit_of_measurement = None
|
||||
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return f"{self._name} {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state."""
|
||||
self._state = self.data[self.kind]
|
||||
if self.kind in [ATTR_API_PM1, ATTR_API_PRESSURE]:
|
||||
self._state = round(self._state)
|
||||
if self.kind in [ATTR_API_TEMPERATURE, ATTR_API_HUMIDITY]:
|
||||
self._state = round(self._state, 1)
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
self._icon = SENSOR_TYPES[self.kind][ATTR_ICON]
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device_class."""
|
||||
return SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique_id for this entity."""
|
||||
return f"{self.airly.latitude}-{self.airly.longitude}-{self.kind.lower()}"
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return SENSOR_TYPES[self.kind][ATTR_UNIT]
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return bool(self.airly.data)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the sensor."""
|
||||
await self.airly.async_update()
|
||||
|
||||
if self.airly.data:
|
||||
self.data = self.airly.data
|
22
homeassistant/components/airly/strings.json
Normal file
22
homeassistant/components/airly/strings.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Airly",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Airly",
|
||||
"description": "Set up Airly air quality integration. To generate API key go to https://developer.airly.eu/register",
|
||||
"data": {
|
||||
"name": "Name of the integration",
|
||||
"api_key": "Airly API key",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"name_exists": "Name already exists.",
|
||||
"wrong_location": "No Airly measuring stations in this area.",
|
||||
"auth": "API key is not correct."
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
"""Support for AirVisual air quality sensors."""
|
||||
from logging import getLogger
|
||||
from datetime import timedelta
|
||||
from logging import getLogger
|
||||
|
||||
from pyairvisual import Client
|
||||
from pyairvisual.errors import AirVisualError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
@ -14,8 +16,8 @@ from homeassistant.const import (
|
||||
CONF_LONGITUDE,
|
||||
CONF_MONITORED_CONDITIONS,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_STATE,
|
||||
CONF_SHOW_ON_MAP,
|
||||
CONF_STATE,
|
||||
)
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@ -97,7 +99,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Configure the platform and add the sensors."""
|
||||
from pyairvisual import Client
|
||||
|
||||
city = config.get(CONF_CITY)
|
||||
state = config.get(CONF_STATE)
|
||||
@ -249,7 +250,6 @@ class AirVisualData:
|
||||
|
||||
async def _async_update(self):
|
||||
"""Update AirVisual data."""
|
||||
from pyairvisual.errors import AirVisualError
|
||||
|
||||
try:
|
||||
if self.city and self.state and self.country:
|
||||
|
@ -1,21 +1,22 @@
|
||||
"""Platform for the Aladdin Connect cover component."""
|
||||
import logging
|
||||
|
||||
from aladdin_connect import AladdinConnectClient
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
CoverDevice,
|
||||
PLATFORM_SCHEMA,
|
||||
SUPPORT_OPEN,
|
||||
SUPPORT_CLOSE,
|
||||
SUPPORT_OPEN,
|
||||
CoverDevice,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
STATE_CLOSED,
|
||||
STATE_OPENING,
|
||||
STATE_CLOSING,
|
||||
STATE_OPEN,
|
||||
STATE_OPENING,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@ -40,7 +41,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Aladdin Connect platform."""
|
||||
from aladdin_connect import AladdinConnectClient
|
||||
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Activa {entity_name} fora",
|
||||
"arm_home": "Activa {entity_name} a casa",
|
||||
"arm_night": "Activa {entity_name} nocturn",
|
||||
"disarm": "Desactiva {entity_name}",
|
||||
"trigger": "Dispara {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"trigger": "Udl\u00f8s {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Arm {entity_name} away",
|
||||
"arm_home": "Arm {entity_name} home",
|
||||
"arm_night": "Arm {entity_name} night",
|
||||
"disarm": "Disarm {entity_name}",
|
||||
"trigger": "Trigger {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Armar {entity_name} exterior",
|
||||
"arm_home": "Armar {entity_name} modo casa",
|
||||
"arm_night": "Armar {entity_name} por la noche",
|
||||
"disarm": "Desarmar {entity_name}",
|
||||
"trigger": "Lanzar {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"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",
|
||||
"disarm": "D\u00e9sarmer {entity_name}",
|
||||
"trigger": "D\u00e9clencheur {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "Armare {entity_name} uscito",
|
||||
"arm_home": "Armare {entity_name} casa",
|
||||
"arm_night": "Armare {entity_name} notte",
|
||||
"disarm": "Disarmare {entity_name}",
|
||||
"trigger": "Attivazione {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "{entity_name} \uc678\ucd9c\uacbd\ube44",
|
||||
"arm_home": "{entity_name} \uc7ac\uc2e4\uacbd\ube44",
|
||||
"arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44",
|
||||
"disarm": "{entity_name} \uacbd\ube44\ud574\uc81c",
|
||||
"trigger": "{entity_name} \ud2b8\ub9ac\uac70"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"arm_away": "{entity_name} fir \u00ebnnerwee uschalten",
|
||||
"arm_home": "{entity_name} fir doheem uschalten",
|
||||
"arm_night": "{entity_name} fir Nuecht uschalten",
|
||||
"disarm": "{entity_name} entsch\u00e4rfen",
|
||||
"trigger": "{entity_name} ausl\u00e9isen"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"device_automation": {
|
||||
"action_type": {
|
||||
"disarm": "Uitschakelen {entity_name}",
|
||||
"trigger": "Trigger {entity_name}"
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user