Merge pull request #28354 from home-assistant/rc

0.101.0
This commit is contained in:
Pascal Vizeli 2019-10-30 21:20:32 +01:00 committed by GitHub
commit 2d7208470e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1436 changed files with 38323 additions and 12192 deletions

View File

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

View File

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

@ -128,3 +128,6 @@ monkeytype.sqlite3
# This is left behind by Azure Restore Cache
tmp_cache
# python-language-server / Rope
.ropeproject

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Abode"
}
}

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

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

View File

@ -0,0 +1,12 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "Endere\u00e7o de e-mail"
}
}
},
"title": ""
}
}

View File

@ -0,0 +1,15 @@
{
"config": {
"error": {
"identifier_exists": "Conta j\u00e1 registada"
},
"step": {
"user": {
"data": {
"username": "Endere\u00e7o de e-mail"
}
}
},
"title": ""
}
}

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

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -0,0 +1,3 @@
"""Constants for the Abode Security System component."""
DOMAIN = "abode"
ATTRIBUTION = "Data provided by goabode.com"

View File

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

View File

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

View File

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

View File

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

View File

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

View 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."
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
"username": "Brukarnamn"
}
}
}
},
"title": "AdGuard Home"
}
}

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "Servidor",
"port": "Porta"
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

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

View File

@ -0,0 +1,14 @@
{
"config": {
"step": {
"user": {
"data": {
"latitude": "Latitude",
"longitude": "Longitude"
},
"title": ""
}
},
"title": ""
}
}

View 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 = {}

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

View 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

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

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

View 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

View 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."
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
{
"device_automation": {
"action_type": {
"trigger": "Udl\u00f8s {entity_name}"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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